diff --git a/assets/flutter_i18n/en.json b/assets/flutter_i18n/en.json index 96e02cec..6e1504f7 100644 --- a/assets/flutter_i18n/en.json +++ b/assets/flutter_i18n/en.json @@ -325,12 +325,15 @@ "background_fetch_notice_title": "Optimize Your AXS Wallet Experience", "background_fetch_notice_text": "To ensure peak performance of the AXS Wallet, we recommend keeping the app active in the background and excluding it from battery optimization settings. This helps maintain seamless functionality and uninterrupted access to your wallet.", "acknowledge": "Acknowledge", - "Background_notifications_service_launched_successfully": "Background notifications service launched successfully.", + "background_notifications_service_launched_successfully": "Background notifications service launched successfully.", "unable_to_launch_background_notification_service": "Unable to launch background notification service.", + "background_notifications_service_disabled_successfully": "Background notifications service disabled successfully.", "low_balance": "Low balance", "expected_transaction_fee": "Expected transaction fee", "expected_epoch_occur": "Expected epoch occurrence", - "background_notifications_frequency": "Background notifications frequency", + "background_notifications": "Background notifications", "occurrence": "Occurrence", - "frequency": "Frequency" + "frequency": "Frequency", + "experiencing_issues": "Experiencing issues?", + "background_service_solution": "Try re-enabling the app or check the battery optimization settings." } \ No newline at end of file diff --git a/lib/features/settings/subfeatures/notifications/domain/background_fetch_config_use_case.dart b/lib/features/settings/subfeatures/notifications/domain/background_fetch_config_use_case.dart index 9b7d92c2..efbaf243 100644 --- a/lib/features/settings/subfeatures/notifications/domain/background_fetch_config_use_case.dart +++ b/lib/features/settings/subfeatures/notifications/domain/background_fetch_config_use_case.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'package:datadashwallet/common/common.dart'; import 'package:datadashwallet/core/core.dart'; import 'package:datadashwallet/features/common/contract/token_contract_use_case.dart'; @@ -5,6 +6,7 @@ import 'package:datadashwallet/features/settings/subfeatures/chain_configuration import 'package:mxc_logic/mxc_logic.dart'; import 'package:background_fetch/background_fetch.dart' as bgFetch; +import '../../../../../main.dart'; import 'background_fetch_config_repository.dart'; class BackgroundFetchConfigUseCase extends ReactiveUseCase { @@ -35,14 +37,14 @@ class BackgroundFetchConfigUseCase extends ReactiveUseCase { void initialize() { _chainConfigurationUseCase.selectedNetwork.listen((network) { - if (network != null && !Config.isMxcChains(network.chainId)) { + final isMXCChains = + network != null && !Config.isMxcChains(network.chainId); + final periodicalCallData = _repository.item; + if (!isMXCChains) { bgFetch.BackgroundFetch.stop(Config.axsPeriodicalTask); + } else if (isMXCChains && periodicalCallData.serviceEnabled) { + startBGFetch(periodicalCallData.duration); } - // else if (network != null && Config.isMxcChains(network.chainId)) { - // isBGFetchEnabled(_repository.item) { - - // } - // } }); } @@ -141,4 +143,55 @@ class BackgroundFetchConfigUseCase extends ReactiveUseCase { periodicalCallData.expectedTransactionFeeEnabled || periodicalCallData.expectedEpochOccurrenceEnabled || periodicalCallData.lowBalanceLimitEnabled; + + // delay is in minutes + Future startBGFetch(int delay) async { + try { + // Stop If any is running + await stopBGFetch(); + + final configurationState = await bgFetch.BackgroundFetch.configure( + bgFetch.BackgroundFetchConfig( + minimumFetchInterval: delay, + stopOnTerminate: false, + enableHeadless: true, + startOnBoot: true, + requiresBatteryNotLow: false, + requiresCharging: false, + requiresStorageNotLow: false, + requiresDeviceIdle: false, + requiredNetworkType: bgFetch.NetworkType.ANY), + callbackDispatcherForeGround); + // Android Only + final backgroundFetchState = + await bgFetch.BackgroundFetch.registerHeadlessTask( + callbackDispatcher); + + final scheduleState = + await bgFetch.BackgroundFetch.scheduleTask(bgFetch.TaskConfig( + taskId: Config.axsPeriodicalTask, + delay: delay * 60 * 1000, + periodic: true, + requiresNetworkConnectivity: true, + startOnBoot: true, + stopOnTerminate: false, + requiredNetworkType: bgFetch.NetworkType.ANY, + )); + + if (scheduleState && + configurationState == bgFetch.BackgroundFetch.STATUS_AVAILABLE || + configurationState == bgFetch.BackgroundFetch.STATUS_RESTRICTED && + (Platform.isAndroid ? backgroundFetchState : true)) { + return true; + } else { + return false; + } + } catch (e) { + return false; + } + } + + Future stopBGFetch() async { + return await bgFetch.BackgroundFetch.stop(Config.axsPeriodicalTask); + } } diff --git a/lib/features/settings/subfeatures/notifications/notificaitons_page.dart b/lib/features/settings/subfeatures/notifications/notificaitons_page.dart index 2436011d..bfe75009 100644 --- a/lib/features/settings/subfeatures/notifications/notificaitons_page.dart +++ b/lib/features/settings/subfeatures/notifications/notificaitons_page.dart @@ -9,6 +9,7 @@ import 'package:mxc_ui/mxc_ui.dart'; import 'notifications_presenter.dart'; import 'notifications_state.dart'; +import 'widgets/bg_service_information_widget.dart'; import 'widgets/switch_row_item.dart'; class NotificationsPage extends HookConsumerWidget { @@ -32,6 +33,10 @@ class NotificationsPage extends HookConsumerWidget { notificationsState.periodicalCallData!.duration); final isMXCChains = Config.isMxcChains(notificationsState.network!.chainId); + final bgServiceEnabled = + notificationsState.periodicalCallData!.serviceEnabled; + + final isSettingsChangeEnabled = isMXCChains && bgServiceEnabled; String translate(String text) => FlutterI18n.translate(context, text); @@ -71,16 +76,20 @@ class NotificationsPage extends HookConsumerWidget { ], ), const SizedBox(height: Sizes.spaceNormal), - Text( - translate('background_notifications_frequency'), - style: FontTheme.of(context).body2.primary(), + SwitchRowItem( + title: translate('background_notifications'), + value: notificationsState.periodicalCallData!.serviceEnabled, + onChanged: notificationsPresenter.changeEnableService, + enabled: isMXCChains, + textTrailingWidget: const BGServiceInformation(), ), const SizedBox(height: Sizes.spaceNormal), MXCDropDown( key: const Key('bgNotificationsFrequencyDropDown'), onTap: notificationsPresenter.showBGFetchFrequencyDialog, selectedItem: frequency.toStringFormatted(), - enabled: isMXCChains, + enabled: isSettingsChangeEnabled && + notificationsState.periodicalCallData!.serviceEnabled, ), const SizedBox(height: Sizes.spaceNormal), SwitchRowItem( @@ -88,7 +97,7 @@ class NotificationsPage extends HookConsumerWidget { value: notificationsState.periodicalCallData!.lowBalanceLimitEnabled, onChanged: notificationsPresenter.enableLowBalanceLimit, - enabled: isMXCChains, + enabled: isSettingsChangeEnabled, ), MxcTextField( key: const ValueKey('lowBalanceTextField'), @@ -97,7 +106,7 @@ class NotificationsPage extends HookConsumerWidget { keyboardType: TextInputType.number, action: TextInputAction.next, suffixText: ref.watch(state).network!.symbol, - readOnly: !isMXCChains || + readOnly: !isSettingsChangeEnabled || !notificationsState.periodicalCallData!.lowBalanceLimitEnabled, hasClearButton: false, validator: (value) { @@ -135,7 +144,7 @@ class NotificationsPage extends HookConsumerWidget { value: notificationsState .periodicalCallData!.expectedTransactionFeeEnabled, onChanged: notificationsPresenter.enableExpectedGasPrice, - enabled: isMXCChains, + enabled: isSettingsChangeEnabled, ), MxcTextField( key: const ValueKey('expectedTransactionFeeTextField'), @@ -144,7 +153,7 @@ class NotificationsPage extends HookConsumerWidget { keyboardType: TextInputType.number, action: TextInputAction.next, suffixText: ref.watch(state).network!.symbol, - readOnly: !isMXCChains || + readOnly: !isSettingsChangeEnabled || !notificationsState .periodicalCallData!.expectedTransactionFeeEnabled, hasClearButton: false, @@ -183,7 +192,7 @@ class NotificationsPage extends HookConsumerWidget { value: notificationsState .periodicalCallData!.expectedEpochOccurrenceEnabled, onChanged: notificationsPresenter.enableExpectedEpochQuantity, - enabled: isMXCChains, + enabled: isSettingsChangeEnabled, ), const SizedBox(height: Sizes.spaceNormal), MXCDropDown( @@ -195,7 +204,7 @@ class NotificationsPage extends HookConsumerWidget { .periodicalCallData!.expectedEpochOccurrence); }, selectedItem: '$expectedEpochOccur Epoch occurrence', - enabled: isMXCChains && + enabled: isSettingsChangeEnabled && notificationsState .periodicalCallData!.expectedEpochOccurrenceEnabled, ), diff --git a/lib/features/settings/subfeatures/notifications/notifications_presenter.dart b/lib/features/settings/subfeatures/notifications/notifications_presenter.dart index 42a9e59a..7037396c 100644 --- a/lib/features/settings/subfeatures/notifications/notifications_presenter.dart +++ b/lib/features/settings/subfeatures/notifications/notifications_presenter.dart @@ -134,6 +134,12 @@ class NotificationsPresenter extends CompletePresenter state.periodicalCallData!.duration)); } + void changeEnableService(bool value) { + final newPeriodicalCallData = + state.periodicalCallData!.copyWith(serviceEnabled: value); + backgroundFetchConfigUseCase.updateItem(newPeriodicalCallData); + } + void enableExpectedGasPrice(bool value) { final newPeriodicalCallData = state.periodicalCallData! .copyWith(expectedTransactionFeeEnabled: value); @@ -176,83 +182,47 @@ class NotificationsPresenter extends CompletePresenter void checkPeriodicalCallDataChange( PeriodicalCallData newPeriodicalCallData) async { - bool newNoneEnabled = - !(newPeriodicalCallData.expectedEpochOccurrenceEnabled || - newPeriodicalCallData.expectedTransactionFeeEnabled || - newPeriodicalCallData.lowBalanceLimitEnabled); - if (state.periodicalCallData != null) { - if (backgroundFetchConfigUseCase.isServicesEnabledStatusChanged( - newPeriodicalCallData, state.periodicalCallData!) && - backgroundFetchConfigUseCase.hasAnyServiceBeenEnabled( - newPeriodicalCallData, state.periodicalCallData!) || - backgroundFetchConfigUseCase.hasDurationChanged( - newPeriodicalCallData, state.periodicalCallData!)) {} - - // none enabled means stopped || was stopped - if (newNoneEnabled == true) { - await stopBGFetch(); - } - // If none was enabled & now one is enabled => Start BG service - // Other wise It was enabled so start BG service in case It's not running - else if (noneEnabled == true && newNoneEnabled == false) { - await showBackgroundFetchAlertDialog(context: context!); - startBGFetch(newPeriodicalCallData.duration); + final isBGServiceChanged = state.periodicalCallData!.serviceEnabled != + newPeriodicalCallData.serviceEnabled; + final bgServiceDurationChanged = + state.periodicalCallData!.duration != newPeriodicalCallData.duration; + + if (isBGServiceChanged && newPeriodicalCallData.serviceEnabled == true) { + startBGFetch( + delay: newPeriodicalCallData.duration, showBGFetchAlert: true); + } else if (isBGServiceChanged && + newPeriodicalCallData.serviceEnabled == false) { + stopBGFetch(showSnackbar: true); + } else if (bgServiceDurationChanged) { + startBGFetch( + delay: newPeriodicalCallData.duration, showBGFetchAlert: false); } } - noneEnabled = newNoneEnabled; + notify(() => state.periodicalCallData = newPeriodicalCallData); } // delay is in minutes - void startBGFetch(int delay) async { - try { - // Stop If any is running - await stopBGFetch(); - - final configurationState = await bgFetch.BackgroundFetch.configure( - bgFetch.BackgroundFetchConfig( - minimumFetchInterval: 15, - stopOnTerminate: false, - enableHeadless: true, - startOnBoot: true, - requiresBatteryNotLow: false, - requiresCharging: false, - requiresStorageNotLow: false, - requiresDeviceIdle: false, - requiredNetworkType: bgFetch.NetworkType.ANY), - callbackDispatcherForeGround); - // Android Only - final backgroundFetchState = - await bgFetch.BackgroundFetch.registerHeadlessTask( - callbackDispatcher); - - final scheduleState = - await bgFetch.BackgroundFetch.scheduleTask(bgFetch.TaskConfig( - taskId: Config.axsPeriodicalTask, - delay: delay * 60 * 1000, - periodic: true, - requiresNetworkConnectivity: true, - startOnBoot: true, - stopOnTerminate: false, - requiredNetworkType: bgFetch.NetworkType.ANY, - )); - - if (scheduleState && - configurationState == bgFetch.BackgroundFetch.STATUS_AVAILABLE || - configurationState == bgFetch.BackgroundFetch.STATUS_RESTRICTED && - (Platform.isAndroid ? backgroundFetchState : true)) { - showBGFetchSuccessSnackBar(); - } else { - showBGFetchFailureSnackBar(); - } - } catch (e) { + void startBGFetch( + {required int delay, required bool showBGFetchAlert}) async { + if (showBGFetchAlert) { + await showBackgroundFetchAlertDialog(context: context!); + } + final success = await backgroundFetchConfigUseCase.startBGFetch(delay); + if (success) { + showBGFetchSuccessSnackBar(); + } else { showBGFetchFailureSnackBar(); } } - Future stopBGFetch() async { - return await bgFetch.BackgroundFetch.stop(Config.axsPeriodicalTask); + Future stopBGFetch({required bool showSnackbar}) async { + final res = await bgFetch.BackgroundFetch.stop(Config.axsPeriodicalTask); + if (showSnackbar) { + showBGFetchDisableSuccessSnackBar(); + } + return res; } void showBGFetchFailureSnackBar() { @@ -266,7 +236,14 @@ class NotificationsPresenter extends CompletePresenter showSnackBar( context: context!, content: translate( - 'Background_notifications_service_launched_successfully')!); + 'background_notifications_service_launched_successfully')!); + } + + void showBGFetchDisableSuccessSnackBar() { + showSnackBar( + context: context!, + content: translate( + 'background_notifications_service_disabled_successfully')!); } @override diff --git a/lib/features/settings/subfeatures/notifications/widgets/bg_service_information_widget.dart b/lib/features/settings/subfeatures/notifications/widgets/bg_service_information_widget.dart new file mode 100644 index 00000000..cf4db771 --- /dev/null +++ b/lib/features/settings/subfeatures/notifications/widgets/bg_service_information_widget.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; +import 'package:mxc_ui/mxc_ui.dart'; + +class BGServiceInformation extends StatelessWidget { + const BGServiceInformation({super.key}); + + @override + Widget build(BuildContext context) { + return Tooltip( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(15)), + color: ColorsTheme.of(context).mainRed, + ), + showDuration: const Duration(seconds: 5), + triggerMode: TooltipTriggerMode.tap, + richMessage: TextSpan( + style: FontTheme.of(context) + .subtitle1() + .copyWith(color: ColorsTheme.of(context).chipTextBlack), + children: [ + TextSpan( + text: FlutterI18n.translate(context, 'experiencing_issues'), + style: FontTheme.of(context) + .subtitle2() + .copyWith(color: ColorsTheme.of(context).chipTextBlack), + ), + const TextSpan(text: ' '), + TextSpan( + text: + FlutterI18n.translate(context, 'background_service_solution'), + style: FontTheme.of(context) + .subtitle1() + .copyWith(color: ColorsTheme.of(context).chipTextBlack), + ), + ]), + preferBelow: false, + child: Icon( + Icons.info_rounded, + color: ColorsTheme.of(context).iconPrimary, + ), + ); + } +} diff --git a/lib/features/settings/subfeatures/notifications/widgets/switch_row_item.dart b/lib/features/settings/subfeatures/notifications/widgets/switch_row_item.dart index 7abfe1f9..790092bd 100644 --- a/lib/features/settings/subfeatures/notifications/widgets/switch_row_item.dart +++ b/lib/features/settings/subfeatures/notifications/widgets/switch_row_item.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:mxc_ui/mxc_ui.dart'; class SwitchRowItem extends StatelessWidget { @@ -7,12 +8,14 @@ class SwitchRowItem extends StatelessWidget { final bool value; final void Function(bool)? onChanged; final bool enabled; + final Widget? textTrailingWidget; const SwitchRowItem( {super.key, required this.title, required this.value, this.onChanged, - required this.enabled}); + required this.enabled, + this.textTrailingWidget}); @override Widget build(BuildContext context) { @@ -22,9 +25,15 @@ class SwitchRowItem extends StatelessWidget { title, style: FontTheme.of(context).body2.primary(), ), + if (textTrailingWidget != null) ...[ + const SizedBox( + width: Sizes.spaceXSmall, + ), + textTrailingWidget! + ], const Spacer(), const SizedBox( - width: 16, + width: Sizes.spaceNormal, ), CupertinoSwitch( value: value, diff --git a/lib/main.dart b/lib/main.dart index 69f0517b..c34ce3a8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -42,57 +42,64 @@ void callbackDispatcher(HeadlessTask task) async { // Foreground void callbackDispatcherForeGround(String taskId) async { - await loadProviders(); - - final container = ProviderContainer(); - final authUseCase = container.read(authUseCaseProvider); - final chainConfigurationUseCase = - container.read(chainConfigurationUseCaseProvider); - final accountUseCase = container.read(accountUseCaseProvider); - final backgroundFetchConfigUseCase = - container.read(backgroundFetchConfigUseCaseProvider); - - final selectedNetwork = - chainConfigurationUseCase.getCurrentNetworkWithoutRefresh(); - PeriodicalCallData periodicalCallData = - backgroundFetchConfigUseCase.periodicalCallData.value; - final chainId = selectedNetwork.chainId; - - final isLoggedIn = authUseCase.loggedIn; - final account = accountUseCase.account.value; - final lowBalanceLimit = periodicalCallData.lowBalanceLimit; - final expectedTransactionFee = periodicalCallData.expectedTransactionFee; - final lowBalanceLimitEnabled = periodicalCallData.lowBalanceLimitEnabled; - final expectedTransactionFeeEnabled = - periodicalCallData.expectedTransactionFeeEnabled; - final lastEpoch = periodicalCallData.lasEpoch; - final expectedEpochOccurrence = periodicalCallData.expectedEpochOccurrence; - final expectedEpochOccurrenceEnabled = - periodicalCallData.expectedEpochOccurrenceEnabled; - - // Make sure user is logged in - if (isLoggedIn && Config.isMxcChains(chainId)) { - AXSNotification().setupFlutterNotifications(shouldInitFirebase: false); - - if (lowBalanceLimitEnabled) { - backgroundFetchConfigUseCase.checkLowBalance(account!, lowBalanceLimit); + try { + await loadProviders(); + + final container = ProviderContainer(); + final authUseCase = container.read(authUseCaseProvider); + final chainConfigurationUseCase = + container.read(chainConfigurationUseCaseProvider); + final accountUseCase = container.read(accountUseCaseProvider); + final backgroundFetchConfigUseCase = + container.read(backgroundFetchConfigUseCaseProvider); + + final selectedNetwork = + chainConfigurationUseCase.getCurrentNetworkWithoutRefresh(); + PeriodicalCallData periodicalCallData = + backgroundFetchConfigUseCase.periodicalCallData.value; + final chainId = selectedNetwork.chainId; + + final isLoggedIn = authUseCase.loggedIn; + final account = accountUseCase.account.value; + final lowBalanceLimit = periodicalCallData.lowBalanceLimit; + final expectedTransactionFee = periodicalCallData.expectedTransactionFee; + final lowBalanceLimitEnabled = periodicalCallData.lowBalanceLimitEnabled; + final expectedTransactionFeeEnabled = + periodicalCallData.expectedTransactionFeeEnabled; + final lastEpoch = periodicalCallData.lasEpoch; + final expectedEpochOccurrence = periodicalCallData.expectedEpochOccurrence; + final expectedEpochOccurrenceEnabled = + periodicalCallData.expectedEpochOccurrenceEnabled; + final serviceEnabled = periodicalCallData.serviceEnabled; + + // Make sure user is logged in + if (isLoggedIn && Config.isMxcChains(chainId) && serviceEnabled) { + AXSNotification().setupFlutterNotifications(shouldInitFirebase: false); + + if (lowBalanceLimitEnabled) { + await backgroundFetchConfigUseCase.checkLowBalance( + account!, lowBalanceLimit); + } + + if (expectedTransactionFeeEnabled) { + await backgroundFetchConfigUseCase + .checkTransactionFee(expectedTransactionFee); + } + + if (expectedEpochOccurrenceEnabled) { + periodicalCallData = await backgroundFetchConfigUseCase.checkEpochOccur( + periodicalCallData, lastEpoch, expectedEpochOccurrence, chainId); + } + + backgroundFetchConfigUseCase.updateItem(periodicalCallData); + BackgroundFetch.finish(taskId); + } else { + // terminate background fetch + BackgroundFetch.stop(taskId); } - - if (expectedTransactionFeeEnabled) { - backgroundFetchConfigUseCase.checkTransactionFee(expectedTransactionFee); - } - - if (expectedEpochOccurrenceEnabled) { - periodicalCallData = await backgroundFetchConfigUseCase.checkEpochOccur( - periodicalCallData, lastEpoch, expectedEpochOccurrence, chainId); - } - - backgroundFetchConfigUseCase.updateItem(periodicalCallData); - } else { - // terminate background fetch - BackgroundFetch.stop(taskId); + } catch (e) { + BackgroundFetch.finish(taskId); } - BackgroundFetch.finish(taskId); } void main() { diff --git a/packages/shared b/packages/shared index 491b0112..b97e7d8e 160000 --- a/packages/shared +++ b/packages/shared @@ -1 +1 @@ -Subproject commit 491b01120e12962d049a4c138c7b7c0710428bbf +Subproject commit b97e7d8e93c355e5bf78026df472379df3cd75e5