Skip to content

Commit 1af5ec3

Browse files
authored
Merge pull request #508 from DevKor-github/codexd/alarm-permission-flow
[codex] Fix alarm and notification permission flows
2 parents f342d48 + bba7d49 commit 1af5ec3

15 files changed

Lines changed: 1309 additions & 235 deletions

File tree

lib/l10n/app_en.arb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
"@allowNotifications": {
5252
"description": "Button text to allow notifications"
5353
},
54+
"allowAlarms": "Allow alarms",
55+
"@allowAlarms": {
56+
"description": "Button text to allow alarm permission"
57+
},
5458
"doItLater": "I'll do it later.",
5559
"@doItLater": {
5660
"description": "Button text to skip a step and do it later"
@@ -63,6 +67,14 @@
6367
"@notificationPermissionDescription": {
6468
"description": "Description explaining why notification permission is needed"
6569
},
70+
"pleaseAllowAlarms": "Please allow alarms",
71+
"@pleaseAllowAlarms": {
72+
"description": "Title asking the user to allow alarm permission"
73+
},
74+
"alarmPermissionDescription": "OnTime uses alarms so preparation starts at the right moment, even when the app is closed.",
75+
"@alarmPermissionDescription": {
76+
"description": "Description explaining why alarm permission is needed"
77+
},
6678
"late": " late",
6779
"@late": {
6880
"description": "Appended to the time when the user is late"
@@ -327,6 +339,14 @@
327339
"@allowAppNotifications": {
328340
"description": "Setting tile for allowing app notifications"
329341
},
342+
"manageAppNotifications": "Manage App Notifications",
343+
"@manageAppNotifications": {
344+
"description": "Dialog title for opening app notification settings"
345+
},
346+
"manageAppNotificationsDescription": "To turn app notifications on or off, update OnTime's notification permission in Settings.",
347+
"@manageAppNotificationsDescription": {
348+
"description": "Dialog description for opening app notification settings"
349+
},
330350
"privacyPolicy": "Privacy Policy",
331351
"@privacyPolicy": {
332352
"description": "Setting tile for opening the privacy policy"

lib/l10n/app_ko.arb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
"am": "오전",
1313
"pm": "오후",
1414
"allowNotifications": "알림 허용하기",
15+
"allowAlarms": "알람 허용하기",
1516
"doItLater": "나중에 할게요.",
1617
"pleaseAllowNotifications": "알림을 허용해주세요",
1718
"notificationPermissionDescription": "약속 준비 리마인더를 보내\n제시간에 준비할 수 있게 도와드려요.",
19+
"pleaseAllowAlarms": "알람을 허용해주세요",
20+
"alarmPermissionDescription": "앱이 닫혀 있어도 약속 준비를 제시간에 시작할 수 있도록 알람을 사용해요.",
1821
"late": " 지각했어요",
1922
"early": " 일찍 준비했어요",
2023
"letsGo": "까먹지 않고 출발",
@@ -76,6 +79,8 @@
7679
"accountSettings": "계정 설정",
7780
"editDefaultPreparation": "기본 준비과정 / 여유시간 수정",
7881
"allowAppNotifications": "앱 알림 허용",
82+
"manageAppNotifications": "앱 알림 관리",
83+
"manageAppNotificationsDescription": "앱 알림을 켜거나 끄려면 설정에서 온타임 알림 권한을 변경해주세요.",
7984
"privacyPolicy": "개인정보 처리방침",
8085
"privacyPolicyOpenError": "개인정보 처리방침을 열 수 없습니다. 잠시 후 다시 시도해주세요.",
8186
"logOut": "로그아웃",

lib/l10n/app_localizations.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ abstract class AppLocalizations {
176176
/// **'Allow notifications'**
177177
String get allowNotifications;
178178

179+
/// Button text to allow alarm permission
180+
///
181+
/// In en, this message translates to:
182+
/// **'Allow alarms'**
183+
String get allowAlarms;
184+
179185
/// Button text to skip a step and do it later
180186
///
181187
/// In en, this message translates to:
@@ -194,6 +200,18 @@ abstract class AppLocalizations {
194200
/// **'OnTime sends schedule preparation reminders so you can get ready on time.'**
195201
String get notificationPermissionDescription;
196202

203+
/// Title asking the user to allow alarm permission
204+
///
205+
/// In en, this message translates to:
206+
/// **'Please allow alarms'**
207+
String get pleaseAllowAlarms;
208+
209+
/// Description explaining why alarm permission is needed
210+
///
211+
/// In en, this message translates to:
212+
/// **'OnTime uses alarms so preparation starts at the right moment, even when the app is closed.'**
213+
String get alarmPermissionDescription;
214+
197215
/// Appended to the time when the user is late
198216
///
199217
/// In en, this message translates to:
@@ -560,6 +578,18 @@ abstract class AppLocalizations {
560578
/// **'Allow App Notifications'**
561579
String get allowAppNotifications;
562580

581+
/// Dialog title for opening app notification settings
582+
///
583+
/// In en, this message translates to:
584+
/// **'Manage App Notifications'**
585+
String get manageAppNotifications;
586+
587+
/// Dialog description for opening app notification settings
588+
///
589+
/// In en, this message translates to:
590+
/// **'To turn app notifications on or off, update OnTime\'s notification permission in Settings.'**
591+
String get manageAppNotificationsDescription;
592+
563593
/// Setting tile for opening the privacy policy
564594
///
565595
/// In en, this message translates to:

lib/l10n/app_localizations_en.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class AppLocalizationsEn extends AppLocalizations {
4949
@override
5050
String get allowNotifications => 'Allow notifications';
5151

52+
@override
53+
String get allowAlarms => 'Allow alarms';
54+
5255
@override
5356
String get doItLater => 'I\'ll do it later.';
5457

@@ -59,6 +62,13 @@ class AppLocalizationsEn extends AppLocalizations {
5962
String get notificationPermissionDescription =>
6063
'OnTime sends schedule preparation reminders so you can get ready on time.';
6164

65+
@override
66+
String get pleaseAllowAlarms => 'Please allow alarms';
67+
68+
@override
69+
String get alarmPermissionDescription =>
70+
'OnTime uses alarms so preparation starts at the right moment, even when the app is closed.';
71+
6272
@override
6373
String get late => ' late';
6474

@@ -270,6 +280,13 @@ class AppLocalizationsEn extends AppLocalizations {
270280
@override
271281
String get allowAppNotifications => 'Allow App Notifications';
272282

283+
@override
284+
String get manageAppNotifications => 'Manage App Notifications';
285+
286+
@override
287+
String get manageAppNotificationsDescription =>
288+
'To turn app notifications on or off, update OnTime\'s notification permission in Settings.';
289+
273290
@override
274291
String get privacyPolicy => 'Privacy Policy';
275292

lib/l10n/app_localizations_ko.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ class AppLocalizationsKo extends AppLocalizations {
4747
@override
4848
String get allowNotifications => '알림 허용하기';
4949

50+
@override
51+
String get allowAlarms => '알람 허용하기';
52+
5053
@override
5154
String get doItLater => '나중에 할게요.';
5255

@@ -57,6 +60,13 @@ class AppLocalizationsKo extends AppLocalizations {
5760
String get notificationPermissionDescription =>
5861
'약속 준비 리마인더를 보내\n제시간에 준비할 수 있게 도와드려요.';
5962

63+
@override
64+
String get pleaseAllowAlarms => '알람을 허용해주세요';
65+
66+
@override
67+
String get alarmPermissionDescription =>
68+
'앱이 닫혀 있어도 약속 준비를 제시간에 시작할 수 있도록 알람을 사용해요.';
69+
6070
@override
6171
String get late => ' 지각했어요';
6272

@@ -251,6 +261,13 @@ class AppLocalizationsKo extends AppLocalizations {
251261
@override
252262
String get allowAppNotifications => '앱 알림 허용';
253263

264+
@override
265+
String get manageAppNotifications => '앱 알림 관리';
266+
267+
@override
268+
String get manageAppNotificationsDescription =>
269+
'앱 알림을 켜거나 끄려면 설정에서 온타임 알림 권한을 변경해주세요.';
270+
254271
@override
255272
String get privacyPolicy => '개인정보 처리방침';
256273

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_bloc/flutter_bloc.dart';
3+
import 'package:flutter_svg/flutter_svg.dart';
4+
import 'package:go_router/go_router.dart';
5+
import 'package:on_time_front/domain/entities/alarm_entities.dart';
6+
import 'package:on_time_front/l10n/app_localizations.dart';
7+
import 'package:on_time_front/presentation/app/cubit/alarm_gate_cubit.dart';
8+
import 'package:on_time_front/presentation/shared/constants/app_colors.dart';
9+
10+
class AlarmAllowScreen extends StatefulWidget {
11+
const AlarmAllowScreen({super.key});
12+
13+
@override
14+
State<AlarmAllowScreen> createState() => _AlarmAllowScreenState();
15+
}
16+
17+
class _AlarmAllowScreenState extends State<AlarmAllowScreen>
18+
with WidgetsBindingObserver {
19+
bool _isRequesting = false;
20+
21+
@override
22+
void initState() {
23+
super.initState();
24+
WidgetsBinding.instance.addObserver(this);
25+
}
26+
27+
@override
28+
void didChangeAppLifecycleState(AppLifecycleState state) {
29+
if (state != AppLifecycleState.resumed) return;
30+
context.read<AlarmGateCubit>().refreshPermission(
31+
disableAlarmsWhenPermissionMissing: true,
32+
enableAlarmsOnGrant: true,
33+
);
34+
}
35+
36+
@override
37+
void dispose() {
38+
WidgetsBinding.instance.removeObserver(this);
39+
super.dispose();
40+
}
41+
42+
Future<void> _requestPermission() async {
43+
setState(() {
44+
_isRequesting = true;
45+
});
46+
final permission = await context.read<AlarmGateCubit>().requestPermission();
47+
if (!mounted) return;
48+
setState(() {
49+
_isRequesting = false;
50+
});
51+
if (permission == AlarmPermissionState.granted ||
52+
permission == AlarmPermissionState.unsupported) {
53+
context.go('/home');
54+
}
55+
}
56+
57+
Future<void> _dismiss() async {
58+
await context.read<AlarmGateCubit>().dismissPrompt();
59+
if (!mounted) return;
60+
context.go('/home');
61+
}
62+
63+
@override
64+
Widget build(BuildContext context) {
65+
return Scaffold(
66+
body: Padding(
67+
padding: const EdgeInsets.only(bottom: 72.0),
68+
child: Column(
69+
mainAxisSize: MainAxisSize.max,
70+
mainAxisAlignment: MainAxisAlignment.start,
71+
crossAxisAlignment: CrossAxisAlignment.center,
72+
spacing: 68.50,
73+
children: [
74+
Expanded(
75+
child: Center(
76+
child: Column(
77+
mainAxisSize: MainAxisSize.min,
78+
mainAxisAlignment: MainAxisAlignment.start,
79+
crossAxisAlignment: CrossAxisAlignment.center,
80+
spacing: 40,
81+
children: const [_Image(), _Title()],
82+
),
83+
),
84+
),
85+
_Buttons(
86+
isRequesting: _isRequesting,
87+
onAllow: _requestPermission,
88+
onDismiss: _dismiss,
89+
),
90+
],
91+
),
92+
),
93+
);
94+
}
95+
}
96+
97+
class _Buttons extends StatelessWidget {
98+
const _Buttons({
99+
required this.isRequesting,
100+
required this.onAllow,
101+
required this.onDismiss,
102+
});
103+
104+
final bool isRequesting;
105+
final Future<void> Function() onAllow;
106+
final Future<void> Function() onDismiss;
107+
108+
@override
109+
Widget build(BuildContext context) {
110+
final textTheme = Theme.of(context).textTheme;
111+
final colorScheme = Theme.of(context).colorScheme;
112+
return Column(
113+
mainAxisSize: MainAxisSize.min,
114+
mainAxisAlignment: MainAxisAlignment.start,
115+
crossAxisAlignment: CrossAxisAlignment.center,
116+
spacing: 24,
117+
children: [
118+
FilledButton(
119+
onPressed: isRequesting ? null : () => onAllow(),
120+
child: Text(
121+
AppLocalizations.of(context)!.allowAlarms,
122+
textAlign: TextAlign.center,
123+
style: textTheme.titleMedium?.copyWith(
124+
color: colorScheme.onPrimary,
125+
),
126+
),
127+
),
128+
GestureDetector(
129+
onTap: isRequesting ? null : () => onDismiss(),
130+
child: SizedBox(
131+
width: 358,
132+
child: Text(
133+
AppLocalizations.of(context)!.doItLater,
134+
textAlign: TextAlign.center,
135+
style: textTheme.bodyLarge?.copyWith(
136+
color: AppColors.grey[400],
137+
decoration: TextDecoration.underline,
138+
decorationColor: AppColors.grey[400],
139+
),
140+
),
141+
),
142+
),
143+
],
144+
);
145+
}
146+
}
147+
148+
class _Title extends StatelessWidget {
149+
const _Title();
150+
151+
@override
152+
Widget build(BuildContext context) {
153+
final textTheme = Theme.of(context).textTheme;
154+
final colorScheme = Theme.of(context).colorScheme;
155+
return Column(
156+
mainAxisSize: MainAxisSize.min,
157+
mainAxisAlignment: MainAxisAlignment.start,
158+
crossAxisAlignment: CrossAxisAlignment.center,
159+
spacing: 12,
160+
children: [
161+
Text(
162+
AppLocalizations.of(context)!.pleaseAllowAlarms,
163+
textAlign: TextAlign.center,
164+
style: textTheme.headlineMedium?.copyWith(color: colorScheme.primary),
165+
),
166+
SizedBox(
167+
width: 282,
168+
child: Text(
169+
AppLocalizations.of(context)!.alarmPermissionDescription,
170+
textAlign: TextAlign.center,
171+
style: textTheme.titleMedium?.copyWith(color: colorScheme.outline),
172+
),
173+
),
174+
],
175+
);
176+
}
177+
}
178+
179+
class _Image extends StatelessWidget {
180+
const _Image();
181+
182+
@override
183+
Widget build(BuildContext context) {
184+
final colorScheme = Theme.of(context).colorScheme;
185+
return Container(
186+
width: 70,
187+
height: 70,
188+
padding: const EdgeInsets.all(17.50),
189+
decoration: ShapeDecoration(
190+
color: colorScheme.primaryContainer,
191+
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(35)),
192+
),
193+
child: SvgPicture.asset(
194+
'bell-ringing.svg',
195+
package: 'assets',
196+
colorFilter: ColorFilter.mode(colorScheme.primary, BlendMode.srcIn),
197+
),
198+
);
199+
}
200+
}

0 commit comments

Comments
 (0)