Skip to content

Commit 0ed0b6e

Browse files
authored
Merge pull request #357 from DevKor-github/feat/add-home-button-to-alarm
Add top-right leave confirmation button to active alarm screen
2 parents 57b835c + 79dc6bb commit 0ed0b6e

2 files changed

Lines changed: 207 additions & 0 deletions

File tree

lib/presentation/alarm/screens/alarm_screen.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import 'package:on_time_front/presentation/app/bloc/schedule/schedule_bloc.dart'
1010
import 'package:on_time_front/presentation/alarm/components/alarm_screen_bottom_section.dart';
1111
import 'package:on_time_front/presentation/alarm/components/alarm_screen_top_section.dart';
1212
import 'package:on_time_front/presentation/alarm/components/preparation_completion_dialog.dart';
13+
import 'package:on_time_front/presentation/shared/components/modal_wide_button.dart';
14+
import 'package:on_time_front/presentation/shared/components/two_action_dialog.dart';
1315
import 'package:on_time_front/presentation/shared/utils/time_format.dart';
1416

1517
class AlarmScreen extends StatefulWidget {
@@ -144,6 +146,30 @@ class _AlarmScreenState extends State<AlarmScreen> {
144146
);
145147
}
146148

149+
Future<void> _showLeaveConfirmationDialog(BuildContext context) async {
150+
final l10n = AppLocalizations.of(context)!;
151+
final result = await showTwoActionDialog(
152+
context,
153+
config: TwoActionDialogConfig(
154+
title: l10n.confirmLeave,
155+
description: l10n.confirmLeaveDescription,
156+
barrierDismissible: false,
157+
secondaryAction: DialogActionConfig(
158+
label: l10n.leave,
159+
variant: ModalWideButtonVariant.neutral,
160+
),
161+
primaryAction: DialogActionConfig(
162+
label: l10n.stay,
163+
variant: ModalWideButtonVariant.primary,
164+
),
165+
),
166+
);
167+
168+
if (result == DialogActionResult.secondary && context.mounted) {
169+
context.go('/home');
170+
}
171+
}
172+
147173
Widget _buildAlarmScreen({
148174
required ScheduleWithPreparationEntity schedule,
149175
}) {
@@ -178,6 +204,21 @@ class _AlarmScreenState extends State<AlarmScreen> {
178204
),
179205
],
180206
),
207+
Positioned(
208+
top: 0,
209+
right: 0,
210+
child: SafeArea(
211+
minimum: EdgeInsets.zero,
212+
child: Padding(
213+
padding: const EdgeInsets.only(right: 4),
214+
child: IconButton(
215+
key: const Key('alarm_close_button'),
216+
icon: const Icon(Icons.close, color: Colors.white),
217+
onPressed: () => _showLeaveConfirmationDialog(context),
218+
),
219+
),
220+
),
221+
),
181222
],
182223
),
183224
);

test/presentation/alarm/screens/preparation_flow_widget_test.dart

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,172 @@ void main() {
599599
expect(find.byIcon(Icons.close), findsOneWidget);
600600
}, timeout: const Timeout(Duration(seconds: 15)));
601601

602+
testWidgets('active alarm screen renders top-corner close button',
603+
(tester) async {
604+
await setLargeTestViewport(tester);
605+
now = DateTime.now();
606+
607+
final schedule = buildSchedule(
608+
id: 's-active-close',
609+
scheduleTime: now.add(const Duration(minutes: 35)),
610+
steps: const [
611+
PreparationStepWithTimeEntity(
612+
id: 'p1',
613+
preparationName: 'Prep',
614+
preparationTime: Duration(minutes: 10),
615+
nextPreparationId: null,
616+
),
617+
],
618+
);
619+
620+
final router = GoRouter(
621+
initialLocation: '/alarmScreen',
622+
routes: [
623+
GoRoute(path: '/home', builder: (_, __) => const Text('HOME')),
624+
GoRoute(
625+
path: '/alarmScreen',
626+
builder: (_, __) => const AlarmScreen(),
627+
),
628+
],
629+
);
630+
631+
final earlyBundle = createEarlyStartUseCaseBundle();
632+
final alarmBloc = ScheduleBloc.test(
633+
StubGetNearestUpcomingScheduleUseCase(() => Stream.value(schedule)),
634+
navigationService,
635+
NoopSaveTimedPreparationUseCase(),
636+
StubGetTimedPreparationSnapshotUseCase({}),
637+
NoopClearTimedPreparationUseCase(),
638+
finishUseCase,
639+
markEarlyStartSessionUseCase: earlyBundle.markUseCase,
640+
getEarlyStartSessionUseCase: earlyBundle.getUseCase,
641+
clearEarlyStartSessionUseCase: earlyBundle.clearUseCase,
642+
nowProvider: () => now,
643+
);
644+
addTearDown(alarmBloc.close);
645+
646+
await pumpWithRouter(tester, bloc: alarmBloc, router: router);
647+
await pumpUntilFound(tester, find.byKey(const Key('alarm_close_button')));
648+
649+
expect(find.byKey(const Key('alarm_close_button')), findsOneWidget);
650+
alarmBloc.add(const ScheduleFinished(0));
651+
await tester.pump();
652+
}, timeout: const Timeout(Duration(seconds: 15)));
653+
654+
testWidgets(
655+
'active alarm close button shows confirm dialog and stay keeps alarm',
656+
(tester) async {
657+
await setLargeTestViewport(tester);
658+
now = DateTime.now();
659+
660+
final schedule = buildSchedule(
661+
id: 's-active-stay',
662+
scheduleTime: now.add(const Duration(minutes: 35)),
663+
steps: const [
664+
PreparationStepWithTimeEntity(
665+
id: 'p1',
666+
preparationName: 'Prep',
667+
preparationTime: Duration(minutes: 10),
668+
nextPreparationId: null,
669+
),
670+
],
671+
);
672+
673+
final router = GoRouter(
674+
initialLocation: '/alarmScreen',
675+
routes: [
676+
GoRoute(path: '/home', builder: (_, __) => const Text('HOME')),
677+
GoRoute(
678+
path: '/alarmScreen',
679+
builder: (_, __) => const AlarmScreen(),
680+
),
681+
],
682+
);
683+
684+
final earlyBundle = createEarlyStartUseCaseBundle();
685+
final alarmBloc = ScheduleBloc.test(
686+
StubGetNearestUpcomingScheduleUseCase(() => Stream.value(schedule)),
687+
navigationService,
688+
NoopSaveTimedPreparationUseCase(),
689+
StubGetTimedPreparationSnapshotUseCase({}),
690+
NoopClearTimedPreparationUseCase(),
691+
finishUseCase,
692+
markEarlyStartSessionUseCase: earlyBundle.markUseCase,
693+
getEarlyStartSessionUseCase: earlyBundle.getUseCase,
694+
clearEarlyStartSessionUseCase: earlyBundle.clearUseCase,
695+
nowProvider: () => now,
696+
);
697+
addTearDown(alarmBloc.close);
698+
699+
await pumpWithRouter(tester, bloc: alarmBloc, router: router);
700+
await tapAndPump(tester, find.byKey(const Key('alarm_close_button')));
701+
await pumpUntilFound(tester, find.byType(TwoActionDialog));
702+
703+
await tapAndPump(tester, find.text("I'll stay"));
704+
await tester.pump(const Duration(milliseconds: 150));
705+
706+
expect(find.text('HOME'), findsNothing);
707+
expect(find.byType(AlarmScreen), findsOneWidget);
708+
alarmBloc.add(const ScheduleFinished(0));
709+
await tester.pump();
710+
}, timeout: const Timeout(Duration(seconds: 15)));
711+
712+
testWidgets('active alarm close button leave action navigates home',
713+
(tester) async {
714+
await setLargeTestViewport(tester);
715+
now = DateTime.now();
716+
717+
final schedule = buildSchedule(
718+
id: 's-active-leave',
719+
scheduleTime: now.add(const Duration(minutes: 35)),
720+
steps: const [
721+
PreparationStepWithTimeEntity(
722+
id: 'p1',
723+
preparationName: 'Prep',
724+
preparationTime: Duration(minutes: 10),
725+
nextPreparationId: null,
726+
),
727+
],
728+
);
729+
730+
final router = GoRouter(
731+
initialLocation: '/alarmScreen',
732+
routes: [
733+
GoRoute(path: '/home', builder: (_, __) => const Text('HOME')),
734+
GoRoute(
735+
path: '/alarmScreen',
736+
builder: (_, __) => const AlarmScreen(),
737+
),
738+
],
739+
);
740+
741+
final earlyBundle = createEarlyStartUseCaseBundle();
742+
final alarmBloc = ScheduleBloc.test(
743+
StubGetNearestUpcomingScheduleUseCase(() => Stream.value(schedule)),
744+
navigationService,
745+
NoopSaveTimedPreparationUseCase(),
746+
StubGetTimedPreparationSnapshotUseCase({}),
747+
NoopClearTimedPreparationUseCase(),
748+
finishUseCase,
749+
markEarlyStartSessionUseCase: earlyBundle.markUseCase,
750+
getEarlyStartSessionUseCase: earlyBundle.getUseCase,
751+
clearEarlyStartSessionUseCase: earlyBundle.clearUseCase,
752+
nowProvider: () => now,
753+
);
754+
addTearDown(alarmBloc.close);
755+
756+
await pumpWithRouter(tester, bloc: alarmBloc, router: router);
757+
await tapAndPump(tester, find.byKey(const Key('alarm_close_button')));
758+
await pumpUntilFound(tester, find.byType(TwoActionDialog));
759+
760+
await tapAndPump(tester, find.text("I'm leaving"));
761+
await pumpUntilRouteText(tester, 'HOME');
762+
763+
expect(find.text('HOME'), findsOneWidget);
764+
alarmBloc.add(const ScheduleFinished(0));
765+
await tester.pump();
766+
}, timeout: const Timeout(Duration(seconds: 15)));
767+
602768
testWidgets(
603769
'manual finish before leave time sends lateness 0 and navigates',
604770
(tester) async {

0 commit comments

Comments
 (0)