Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -339,14 +339,6 @@
"@allowAppNotifications": {
"description": "Setting tile for allowing app notifications"
},
"manageAppNotifications": "Manage App Notifications",
"@manageAppNotifications": {
"description": "Dialog title for opening app notification settings"
},
"manageAppNotificationsDescription": "To turn app notifications on or off, update OnTime's notification permission in Settings.",
"@manageAppNotificationsDescription": {
"description": "Dialog description for opening app notification settings"
},
"privacyPolicy": "Privacy Policy",
"@privacyPolicy": {
"description": "Setting tile for opening the privacy policy"
Expand Down
2 changes: 0 additions & 2 deletions lib/l10n/app_ko.arb
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,6 @@
"accountSettings": "계정 설정",
"editDefaultPreparation": "기본 준비과정 / 여유시간 수정",
"allowAppNotifications": "앱 알림 허용",
"manageAppNotifications": "앱 알림 관리",
"manageAppNotificationsDescription": "앱 알림을 켜거나 끄려면 설정에서 온타임 알림 권한을 변경해주세요.",
"privacyPolicy": "개인정보 처리방침",
"privacyPolicyOpenError": "개인정보 처리방침을 열 수 없습니다. 잠시 후 다시 시도해주세요.",
"logOut": "로그아웃",
Expand Down
12 changes: 0 additions & 12 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -578,18 +578,6 @@ abstract class AppLocalizations {
/// **'Allow App Notifications'**
String get allowAppNotifications;

/// Dialog title for opening app notification settings
///
/// In en, this message translates to:
/// **'Manage App Notifications'**
String get manageAppNotifications;

/// Dialog description for opening app notification settings
///
/// In en, this message translates to:
/// **'To turn app notifications on or off, update OnTime\'s notification permission in Settings.'**
String get manageAppNotificationsDescription;

/// Setting tile for opening the privacy policy
///
/// In en, this message translates to:
Expand Down
7 changes: 0 additions & 7 deletions lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get allowAppNotifications => 'Allow App Notifications';

@override
String get manageAppNotifications => 'Manage App Notifications';

@override
String get manageAppNotificationsDescription =>
'To turn app notifications on or off, update OnTime\'s notification permission in Settings.';

@override
String get privacyPolicy => 'Privacy Policy';

Expand Down
7 changes: 0 additions & 7 deletions lib/l10n/app_localizations_ko.dart
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,6 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get allowAppNotifications => '앱 알림 허용';

@override
String get manageAppNotifications => '앱 알림 관리';

@override
String get manageAppNotificationsDescription =>
'앱 알림을 켜거나 끄려면 설정에서 온타임 알림 권한을 변경해주세요.';

@override
String get privacyPolicy => '개인정보 처리방침';

Expand Down
244 changes: 112 additions & 132 deletions lib/presentation/my_page/my_page_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:on_time_front/core/constants/external_links.dart';
import 'package:on_time_front/core/di/di_setup.dart';
import 'package:on_time_front/core/services/alarm_scheduler_service.dart';
import 'package:on_time_front/core/services/fallback_alarm_notification_service.dart';
import 'package:on_time_front/core/services/notification_service.dart';
import 'package:on_time_front/domain/entities/alarm_entities.dart';
import 'package:on_time_front/domain/entities/preparation_entity.dart';
import 'package:on_time_front/domain/repositories/alarm_registry_repository.dart';
Expand All @@ -17,23 +18,16 @@ import 'package:on_time_front/l10n/app_localizations.dart';
import 'package:on_time_front/presentation/app/bloc/auth/auth_bloc.dart';
import 'package:on_time_front/presentation/my_page/my_page_modal/delete_user_modal.dart';
import 'package:on_time_front/presentation/my_page/my_page_modal/logout_modal.dart';
import 'package:on_time_front/presentation/notification_allow/screens/notification_allow_screen.dart';
import 'package:on_time_front/presentation/shared/components/modal_wide_button.dart';
import 'package:on_time_front/presentation/shared/components/two_action_dialog.dart';

typedef PrivacyPolicyLauncher = Future<bool> Function(Uri uri);

class MyPageScreen extends StatelessWidget {
const MyPageScreen({
super.key,
PrivacyPolicyLauncher? openPrivacyPolicy,
NotificationPermissionGateway notificationPermissionGateway =
const NotificationServicePermissionGateway(),
}) : _openPrivacyPolicy = openPrivacyPolicy,
_notificationPermissionGateway = notificationPermissionGateway;
const MyPageScreen({super.key, PrivacyPolicyLauncher? openPrivacyPolicy})
: _openPrivacyPolicy = openPrivacyPolicy;

final PrivacyPolicyLauncher? _openPrivacyPolicy;
final NotificationPermissionGateway _notificationPermissionGateway;

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -98,8 +92,11 @@ class MyPageScreen extends StatelessWidget {
if (updatedPreparation != null) {}
},
),
_NotificationStatusView(
permissionGateway: _notificationPermissionGateway,
_SettingTile(
title: AppLocalizations.of(context)!.allowAppNotifications,
onTap: () async {
await _handleNotificationPermission(context);
},
),
_SettingTile(
title: AppLocalizations.of(context)!.privacyPolicy,
Expand Down Expand Up @@ -305,118 +302,7 @@ class _AlarmStatusViewState extends State<_AlarmStatusView> {
),
],
),
Switch(
key: const Key('alarm_permission_switch'),
value: _alarmsEnabled,
onChanged: _isUpdating ? null : _toggle,
),
],
);
}
}

class _NotificationStatusView extends StatefulWidget {
const _NotificationStatusView({required this.permissionGateway});

final NotificationPermissionGateway permissionGateway;

@override
State<_NotificationStatusView> createState() =>
_NotificationStatusViewState();
}

class _NotificationStatusViewState extends State<_NotificationStatusView>
with WidgetsBindingObserver {
bool _isLoading = true;
bool _isUpdating = false;
bool _notificationsEnabled = false;

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_load();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state != AppLifecycleState.resumed) return;
_load();
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

Future<void> _load() async {
setState(() {
_isLoading = true;
});
final status = await widget.permissionGateway.checkNotificationPermission();
if (!mounted) return;
setState(() {
_notificationsEnabled = _isNotificationAllowed(status);
_isLoading = false;
});
}

Future<void> _toggle(bool value) async {
setState(() {
_isUpdating = true;
_notificationsEnabled = value;
});
try {
if (value) {
final status = await widget.permissionGateway.requestPermission();
if (_isNotificationAllowed(status)) {
await widget.permissionGateway.initializeNotifications();
}
} else {
final shouldOpenSettings = await _showNotificationSettingsDialog(
context,
);
if (shouldOpenSettings == true) {
await widget.permissionGateway.openNotificationSettings();
}
}
await _load();
} finally {
if (mounted) {
setState(() {
_isUpdating = false;
});
}
}
}

@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final colorScheme = Theme.of(context).colorScheme;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppLocalizations.of(context)!.allowAppNotifications,
style: textTheme.bodyLarge,
),
const SizedBox(height: 4),
Text(
_isLoading ? '확인 중' : (_notificationsEnabled ? '켜짐' : '꺼짐'),
style: textTheme.bodySmall?.copyWith(color: colorScheme.outline),
),
],
),
Switch(
key: const Key('notification_permission_switch'),
value: _notificationsEnabled,
onChanged: _isUpdating ? null : _toggle,
),
Switch(value: _alarmsEnabled, onChanged: _isUpdating ? null : _toggle),
],
);
}
Expand All @@ -427,11 +313,6 @@ bool _needsExactAlarmRecovery(AlarmPermissionState permission) {
permission == AlarmPermissionState.notDetermined;
}

bool _isNotificationAllowed(AuthorizationStatus status) {
return status == AuthorizationStatus.authorized ||
status == AuthorizationStatus.provisional;
}

class _MyAccountView extends StatelessWidget {
const _MyAccountView();

Expand Down Expand Up @@ -537,6 +418,87 @@ class _SettingTile extends StatelessWidget {
}
}

Future<void> _handleNotificationPermission(BuildContext context) async {
final notificationService = NotificationService.instance;
final currentStatus = await notificationService.checkNotificationPermission();

if (!context.mounted) return;

if (currentStatus == AuthorizationStatus.authorized) {
await _showAlreadyEnabledDialog(context);
} else if (currentStatus == AuthorizationStatus.denied) {
final shouldRequest = await _showPermissionRationaleDialog(context);
if (shouldRequest == true && context.mounted) {
final newStatus = await notificationService.requestPermission();

if (!context.mounted) return;

if (newStatus == AuthorizationStatus.authorized) {
await notificationService.initialize();
if (!context.mounted) return;
await _showPermissionGrantedDialog(context);
} else if (newStatus == AuthorizationStatus.denied) {
await _showGoToSettingsDialog(context);
}
}
} else if (currentStatus == AuthorizationStatus.notDetermined) {
final shouldRequest = await _showPermissionRationaleDialog(context);
if (shouldRequest == true && context.mounted) {
final newStatus = await notificationService.requestPermission();

if (!context.mounted) return;

if (newStatus == AuthorizationStatus.authorized) {
await notificationService.initialize();
if (!context.mounted) return;
await _showPermissionGrantedDialog(context);
} else if (newStatus == AuthorizationStatus.denied) {
await _showGoToSettingsDialog(context);
}
}
} else {
await _showGoToSettingsDialog(context);
}
}

Future<void> _showAlreadyEnabledDialog(BuildContext context) async {
final l10n = AppLocalizations.of(context)!;

await showTwoActionDialog(
context,
config: TwoActionDialogConfig(
title: l10n.notificationAlreadyEnabled,
description: l10n.notificationAlreadyEnabledDescription,
primaryAction: DialogActionConfig(
label: l10n.ok,
variant: ModalWideButtonVariant.primary,
),
),
);
}

Future<bool?> _showPermissionRationaleDialog(BuildContext context) async {
final l10n = AppLocalizations.of(context)!;

final result = await showTwoActionDialog(
context,
config: TwoActionDialogConfig(
title: l10n.notificationPermissionRequired,
description: l10n.notificationPermissionRequiredDescription,
secondaryAction: DialogActionConfig(
label: l10n.cancel,
variant: ModalWideButtonVariant.neutral,
),
primaryAction: DialogActionConfig(
label: l10n.allow,
variant: ModalWideButtonVariant.primary,
),
),
);

return result == DialogActionResult.primary;
}

Future<DialogActionResult?> _showExactAlarmPermissionDialog(
BuildContext context,
) async {
Expand All @@ -559,14 +521,30 @@ Future<DialogActionResult?> _showExactAlarmPermissionDialog(
);
}

Future<bool?> _showNotificationSettingsDialog(BuildContext context) async {
Future<void> _showPermissionGrantedDialog(BuildContext context) async {
final l10n = AppLocalizations.of(context)!;

await showTwoActionDialog(
context,
config: TwoActionDialogConfig(
title: l10n.notificationPermissionGranted,
description: l10n.notificationPermissionGrantedDescription,
primaryAction: DialogActionConfig(
label: l10n.ok,
variant: ModalWideButtonVariant.primary,
),
),
);
}

Future<void> _showGoToSettingsDialog(BuildContext context) async {
final l10n = AppLocalizations.of(context)!;

final result = await showTwoActionDialog(
context,
config: TwoActionDialogConfig(
title: l10n.manageAppNotifications,
description: l10n.manageAppNotificationsDescription,
title: l10n.openNotificationSettings,
description: l10n.openNotificationSettingsDescription,
secondaryAction: DialogActionConfig(
label: l10n.cancel,
variant: ModalWideButtonVariant.neutral,
Expand All @@ -578,5 +556,7 @@ Future<bool?> _showNotificationSettingsDialog(BuildContext context) async {
),
);

return result == DialogActionResult.primary;
if (result == DialogActionResult.primary) {
await NotificationService.instance.openNotificationSettings();
}
}
Loading
Loading