Skip to content

Commit cb96df2

Browse files
authored
Merge pull request #367 from DevKor-github/codex/fix-preparation-boundary
[codex] Fix preparation start boundary
2 parents c545ae4 + 2794529 commit cb96df2

2 files changed

Lines changed: 83 additions & 6 deletions

File tree

lib/presentation/app/bloc/schedule/schedule_bloc.dart

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,14 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
179179
}
180180

181181
_activeEarlyStartScheduleId = null;
182-
if (_isPreparationOnGoing(resolvedSchedule)) {
182+
if (_isAtPreparationStartBoundary(resolvedSchedule, now)) {
183+
emit(ScheduleState.upcoming(resolvedSchedule));
184+
debugPrint('preparation boundary reached: $resolvedSchedule');
185+
add(const ScheduleStarted());
186+
return;
187+
}
188+
189+
if (_isPreparationOnGoing(resolvedSchedule, now)) {
183190
emit(ScheduleState.ongoing(resolvedSchedule));
184191
debugPrint(
185192
'ongoingSchedule: $resolvedSchedule, currentStep: ${resolvedSchedule.preparation.currentStep}');
@@ -267,15 +274,20 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
267274
_activeEarlyStartScheduleId = null;
268275
_lastSnapshotSavedAt = null;
269276
emit(const ScheduleState.notExists());
270-
} catch (_) {
271-
debugPrint('error finishing schedule: $_');
277+
} catch (error) {
278+
debugPrint('error finishing schedule: $error');
272279
}
273280
}
274281

275282
void _startScheduleTimer(ScheduleWithPreparationEntity schedule) {
276283
final now = _nowProvider();
277284
final target = schedule.preparationStartTime;
278-
if (!target.isAfter(now)) return;
285+
if (!target.isAfter(now)) {
286+
if (!isClosed && _currentScheduleId == schedule.id) {
287+
add(const ScheduleStarted());
288+
}
289+
return;
290+
}
279291
final duration = target.difference(now);
280292
_scheduleStartTimer = Timer(duration, () {
281293
// Only add event if bloc is still active and schedule ID matches
@@ -363,9 +375,18 @@ class ScheduleBloc extends Bloc<ScheduleEvent, ScheduleState> {
363375
await _clearEarlyStartSessionUseCase(scheduleId);
364376
}
365377

366-
bool _isPreparationOnGoing(ScheduleWithPreparationEntity schedule) {
378+
bool _isAtPreparationStartBoundary(
379+
ScheduleWithPreparationEntity schedule,
380+
DateTime now,
381+
) {
382+
return schedule.preparationStartTime.isAtSameMomentAs(now);
383+
}
384+
385+
bool _isPreparationOnGoing(
386+
ScheduleWithPreparationEntity schedule,
387+
DateTime now,
388+
) {
367389
final start = schedule.preparationStartTime;
368-
final now = _nowProvider();
369390
return start.isBefore(now) && schedule.scheduleTime.isAfter(now);
370391
}
371392

test/presentation/app/bloc/schedule/schedule_bloc_test.dart

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,62 @@ void main() {
301301
expect(navigationService.pushedRoutes, ['/scheduleStart']);
302302
});
303303

304+
test(
305+
'when received exactly at preparationStartTime it starts and navigates once',
306+
() async {
307+
final schedule = buildSchedule(
308+
id: 'exact-boundary',
309+
scheduleTime: now.add(const Duration(minutes: 40)),
310+
steps: const [
311+
PreparationStepWithTimeEntity(
312+
id: 'a',
313+
preparationName: 'a',
314+
preparationTime: Duration(minutes: 10),
315+
nextPreparationId: null,
316+
),
317+
],
318+
);
319+
expect(schedule.preparationStartTime, now);
320+
321+
bloc.add(ScheduleUpcomingReceived(schedule));
322+
await Future<void>.delayed(Duration.zero);
323+
await Future<void>.delayed(Duration.zero);
324+
325+
expect(bloc.state.status, ScheduleStatus.started);
326+
expect(bloc.state.schedule?.id, 'exact-boundary');
327+
expect(bloc.state.isEarlyStarted, isFalse);
328+
expect(navigationService.pushedRoutes, ['/scheduleStart']);
329+
});
330+
331+
test(
332+
'when early-started schedule is received at boundary it does not navigate again',
333+
() async {
334+
final schedule = buildSchedule(
335+
id: 'early-boundary',
336+
scheduleTime: now.add(const Duration(minutes: 40)),
337+
steps: const [
338+
PreparationStepWithTimeEntity(
339+
id: 'a',
340+
preparationName: 'a',
341+
preparationTime: Duration(minutes: 10),
342+
nextPreparationId: null,
343+
),
344+
],
345+
);
346+
expect(schedule.preparationStartTime, now);
347+
markEarlySessionUseCase.sessions['early-boundary'] =
348+
now.subtract(const Duration(minutes: 1));
349+
350+
bloc.add(ScheduleUpcomingReceived(schedule));
351+
await Future<void>.delayed(Duration.zero);
352+
await Future<void>.delayed(Duration.zero);
353+
354+
expect(bloc.state.status, ScheduleStatus.started);
355+
expect(bloc.state.schedule?.id, 'early-boundary');
356+
expect(bloc.state.isEarlyStarted, isTrue);
357+
expect(navigationService.pushedRoutes, isEmpty);
358+
});
359+
304360
test('late entry fast-forwards elapsed preparation to current step',
305361
() async {
306362
final schedule = buildSchedule(

0 commit comments

Comments
 (0)