From 69e6049532c8239546b033603e0066c25cb65ab7 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Sun, 1 Dec 2024 09:16:46 +0200 Subject: [PATCH 01/17] fix: Improve Quran Download and Navigation Experience (#1452) * fix: Ensure correct Moshaf type (Hafs) is displayed after download * fix: display Hafs Quran correctly and remove success dialog - Set Hafs as default Moshaf type if none is selected. - Auto-dismiss success dialog on download completion. - Improved state invalidation for Quran reading updates. - Added FocusNode for better dialog interaction. - Optimized resource management with keepAlive and link.close(). * fix: improve Quran Download and Navigation Experience - Redirect user to Quran reading screen automatically after successful download and extraction of Quran (Hafs). - Remove the unnecessary "OK" button to confirm Quran download completion, streamlining the user experience. - Enhance state management for download-related UI in `quran_reading_screen.dart` to handle various download states (needed, downloading, extracting). - Update `download_quran_popup.dart` to ensure proper navigation based on the user's first-time download experience. - Improve error handling and loading indicators for a smoother and more intuitive flow. * fix formating --- .../quran/reading/quran_reading_screen.dart | 74 +++--- .../quran/widget/download_quran_popup.dart | 219 +++++++++++------- .../download_quran_notifier.dart | 18 +- .../quran/reading/quran_reading_notifer.dart | 87 ++++--- 4 files changed, 244 insertions(+), 154 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 74fda1aea..db51bcbfb 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -247,7 +247,6 @@ class _QuranReadingScreenState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) async { ref.read(downloadQuranNotifierProvider); - ref.read(quranReadingNotifierProvider); }); } @@ -321,38 +320,51 @@ class _QuranReadingScreenState extends ConsumerState { }); final autoReadingState = ref.watch(autoScrollNotifierProvider); - - return WillPopScope( - onWillPop: () async { - userPrefs.orientationLandscape = true; - return true; - }, - child: quranReadingState.when( - data: (state) { - setState(() { - _isRotated = state.isRotated; - }); - return RotatedBox( - quarterTurns: state.isRotated ? -1 : 0, - child: SizedBox( - width: MediaQuery.of(context).size.height, - height: MediaQuery.of(context).size.width, - child: Scaffold( - backgroundColor: Colors.white, - floatingActionButtonLocation: _getFloatingActionButtonLocation(context), - floatingActionButton: QuranFloatingActionControls( - switchScreenViewFocusNode: _switchScreenViewFocusNode, - switchQuranModeNode: _switchQuranModeNode, - switchToPlayQuranFocusNode: _switchToPlayQuranFocusNode, - ), - body: _buildBody(quranReadingState, state.isRotated, userPrefs, autoReadingState), - ), + final downloadState = ref.watch(downloadQuranNotifierProvider); + return downloadState.when( + data: (data) { + if (data is NeededDownloadedQuran || data is Downloading || data is Extracting) { + return Scaffold( + body: Container( + color: Colors.white, ), ); - }, - loading: () => SizedBox(), - error: (error, stack) => const Icon(Icons.error), - ), + } + return WillPopScope( + onWillPop: () async { + userPrefs.orientationLandscape = true; + return true; + }, + child: quranReadingState.when( + data: (state) { + setState(() { + _isRotated = state.isRotated; + }); + return RotatedBox( + quarterTurns: state.isRotated ? -1 : 0, + child: SizedBox( + width: MediaQuery.of(context).size.height, + height: MediaQuery.of(context).size.width, + child: Scaffold( + backgroundColor: Colors.white, + floatingActionButtonLocation: _getFloatingActionButtonLocation(context), + floatingActionButton: QuranFloatingActionControls( + switchScreenViewFocusNode: _switchScreenViewFocusNode, + switchQuranModeNode: _switchQuranModeNode, + switchToPlayQuranFocusNode: _switchToPlayQuranFocusNode, + ), + body: _buildBody(quranReadingState, state.isRotated, userPrefs, autoReadingState), + ), + ), + ); + }, + loading: () => Scaffold(body: SizedBox()), + error: (error, stack) => Scaffold(body: const Icon(Icons.error)), + ), + ); + }, + loading: () => Scaffold(body: _buildLoadingIndicator()), + error: (error, stack) => Scaffold(body: _buildErrorIndicator(error)), ); } diff --git a/lib/src/pages/quran/widget/download_quran_popup.dart b/lib/src/pages/quran/widget/download_quran_popup.dart index a1e0db5ca..26ea12b8a 100644 --- a/lib/src/pages/quran/widget/download_quran_popup.dart +++ b/lib/src/pages/quran/widget/download_quran_popup.dart @@ -8,7 +8,7 @@ import 'package:mawaqit/src/domain/model/quran/moshaf_type_model.dart'; import 'package:mawaqit/src/state_management/quran/download_quran/download_quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/download_quran/download_quran_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/moshaf_type_notifier.dart'; -import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; +import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; class DownloadQuranDialog extends ConsumerStatefulWidget { const DownloadQuranDialog({super.key}); @@ -19,15 +19,23 @@ class DownloadQuranDialog extends ConsumerStatefulWidget { class _DownloadQuranDialogState extends ConsumerState { MoshafType selectedMoshafType = MoshafType.hafs; + late FocusNode _dialogFocusNode; @override void initState() { super.initState(); + _dialogFocusNode = FocusNode(); WidgetsBinding.instance.addPostFrameCallback((_) { _checkForUpdate(); }); } + @override + void dispose() { + _dialogFocusNode.dispose(); + super.dispose(); + } + void _checkForUpdate() { final notifier = ref.read(downloadQuranNotifierProvider.notifier); // notifier.checkForUpdate(notifier.selectedMoshafType); @@ -35,14 +43,34 @@ class _DownloadQuranDialogState extends ConsumerState { @override Widget build(BuildContext context) { - final state = ref.watch(downloadQuranNotifierProvider); - return state.when( - data: (data) => _buildContent(context, data), - loading: () => Container(), - error: (error, _) => _buildErrorDialog(context, error), + final downloadState = ref.watch(downloadQuranNotifierProvider); + + return downloadState.when( + data: (data) => _buildDialogContent(context, data), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => _buildErrorDialog(context, error), ); } + Widget _buildDialogContent(BuildContext context, DownloadQuranState state) { + return switch (state) { + NeededDownloadedQuran() => _buildChooseDownloadMoshaf(context), + Downloading() => _buildDownloadingDialog(context, state), + Extracting() => _buildExtractingDialog(context, state), + Success() => _handleSuccess(context), + CancelDownload() => const SizedBox(), + _ => const SizedBox(), + }; + } + + Widget _handleSuccess(BuildContext context) { + // Auto close dialog on success + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(context).pop(); + }); + return const SizedBox(); + } + Widget _buildContent(BuildContext context, DownloadQuranState state) { // return Container(); return switch (state) { @@ -50,7 +78,7 @@ class _DownloadQuranDialogState extends ConsumerState { // UpdateAvailable() => _buildUpdateAvailableDialog(context, state), Downloading() => _buildDownloadingDialog(context, state), Extracting() => _buildExtractingDialog(context, state), - Success() => _buildSuccessDialog(context, state), + Success() => _successDialog(context), CancelDownload() => Container(), // NoUpdate() => _buildNoUpdateDialog(context, state), _ => Container(), @@ -79,63 +107,68 @@ class _DownloadQuranDialogState extends ConsumerState { } Widget _buildDownloadingDialog(BuildContext context, Downloading state) { - return AlertDialog( - title: Text(S.of(context).downloadingQuran), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - LinearProgressIndicator(value: state.progress / 100), - SizedBox(height: 16), - Text('${state.progress.toStringAsFixed(2)}%'), - ], - ), - actions: [ - TextButton( - autofocus: true, - onPressed: () async { - final notifier = ref.read(downloadQuranNotifierProvider.notifier); - ref.read(moshafTypeNotifierProvider).maybeWhen( - orElse: () {}, - data: (state) async { - state.selectedMoshaf.fold(() { - return null; - }, (selectedMoshaf) async { - await notifier.cancelDownload(selectedMoshaf); // Await cancellation - Navigator.pop(context); // Close dialog after cancel completes - }); - }, - ); - }, - child: Text(S.of(context).cancel), + return Focus( + focusNode: _dialogFocusNode, + child: AlertDialog( + title: Text(S.of(context).downloadingQuran), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + LinearProgressIndicator(value: state.progress / 100), + SizedBox(height: 16), + Text('${state.progress.toStringAsFixed(2)}%'), + ], ), - ], - ); - } + actions: [ + TextButton( + autofocus: true, + onPressed: () async { + final notifier = ref.read(downloadQuranNotifierProvider.notifier); + final moshafType = ref.watch(moshafTypeNotifierProvider); - Widget _buildExtractingDialog(BuildContext context, Extracting state) { - return AlertDialog( - title: Text(S.of(context).extractingQuran), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - LinearProgressIndicator(value: state.progress / 100), - SizedBox(height: 16), - Text('${state.progress.toStringAsFixed(2)}%'), + ref.read(moshafTypeNotifierProvider).maybeWhen( + orElse: () {}, + data: (state) async { + state.selectedMoshaf.fold(() { + return null; + }, (selectedMoshaf) async { + await notifier.cancelDownload(selectedMoshaf); // Await cancellation + }); + }, + ); + moshafType.when( + data: (data) { + if (data.isFirstTime) { + Navigator.popUntil(context, (route) => route.isFirst); + } else { + Navigator.pop(context); + } + }, + error: (_, __) {}, + loading: () {}, + ); + }, + child: Text(S.of(context).cancel), + ), ], ), ); } - Widget _buildSuccessDialog(BuildContext context, Success state) { - return AlertDialog( - title: Text(S.of(context).quranDownloaded), - actions: [ - TextButton( - autofocus: true, - onPressed: () => Navigator.pop(context), - child: Text(S.of(context).ok), + Widget _buildExtractingDialog(BuildContext context, Extracting state) { + return Focus( + focusNode: _dialogFocusNode, + child: AlertDialog( + title: Text(S.of(context).extractingQuran), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + LinearProgressIndicator(value: state.progress / 100), + SizedBox(height: 16), + Text('${state.progress.toStringAsFixed(2)}%'), + ], ), - ], + ), ); } @@ -152,41 +185,46 @@ class _DownloadQuranDialogState extends ConsumerState { } Widget _buildChooseDownloadMoshaf(BuildContext context) { - return AlertDialog( - title: Text(S.of(context).chooseQuranType), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildMoshafTypeRadio( - context, - title: S.of(context).warsh, - value: MoshafType.warsh, - setState: setState, + return Focus( + focusNode: _dialogFocusNode, + child: AlertDialog( + title: Text(S.of(context).chooseQuranType), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildMoshafTypeRadio( + context, + title: S.of(context).warsh, + value: MoshafType.warsh, + setState: setState, + autofocus: selectedMoshafType == MoshafType.warsh, + ), + _buildMoshafTypeRadio( + context, + title: S.of(context).hafs, + value: MoshafType.hafs, + setState: setState, + autofocus: selectedMoshafType == MoshafType.hafs, + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text(S.of(context).cancel), ), - _buildMoshafTypeRadio( - context, - title: S.of(context).hafs, - value: MoshafType.hafs, - setState: setState, + TextButton( + autofocus: true, + onPressed: () async { + Navigator.pop(context); + await ref.read(downloadQuranNotifierProvider.notifier).downloadQuran(selectedMoshafType); + }, + child: Text(S.of(context).download), ), ], ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text(S.of(context).cancel), - ), - TextButton( - autofocus: true, - onPressed: () async { - Navigator.pop(context); - await ref.read(downloadQuranNotifierProvider.notifier).downloadQuran(selectedMoshafType); - }, - child: Text(S.of(context).download), - ), - ], ); } @@ -195,11 +233,12 @@ class _DownloadQuranDialogState extends ConsumerState { required String title, required MoshafType value, required void Function(VoidCallback fn) setState, + bool autofocus = false, }) { return RadioListTile( title: Text(title), value: value, - autofocus: true, + autofocus: autofocus, groupValue: selectedMoshafType, onChanged: (MoshafType? selected) { setState(() { @@ -232,4 +271,8 @@ class _DownloadQuranDialogState extends ConsumerState { ], ); } + + Widget _successDialog(BuildContext context) { + return Container(); + } } diff --git a/lib/src/state_management/quran/download_quran/download_quran_notifier.dart b/lib/src/state_management/quran/download_quran/download_quran_notifier.dart index 74d22929e..46e535360 100644 --- a/lib/src/state_management/quran/download_quran/download_quran_notifier.dart +++ b/lib/src/state_management/quran/download_quran/download_quran_notifier.dart @@ -131,13 +131,23 @@ class DownloadQuranNotifier extends AutoDisposeAsyncNotifier } Future downloadQuran(MoshafType moshafType) async { - state = const AsyncLoading(); + final link = ref.keepAlive(); // Keep alive during download + try { + state = const AsyncLoading(); + + // First ensure moshaf type is selected + await ref.read(moshafTypeNotifierProvider.notifier).selectMoshafType(moshafType); + final downloadState = await _downloadQuran(moshafType); if (downloadState is Success) { await ref.read(moshafTypeNotifierProvider.notifier).setNotFirstTime(); - } - if (downloadState is! CancelDownload) { + + state = AsyncData(downloadState); + + // Force rebuild reading provider in next frame + ref.invalidate(quranReadingNotifierProvider); + } else if (downloadState is! CancelDownload) { state = AsyncData(downloadState); } } catch (e, s) { @@ -145,6 +155,8 @@ class DownloadQuranNotifier extends AutoDisposeAsyncNotifier return; } state = AsyncError(e, s); + } finally { + link.close(); } } diff --git a/lib/src/state_management/quran/reading/quran_reading_notifer.dart b/lib/src/state_management/quran/reading/quran_reading_notifer.dart index d6fd02e25..b23bcc3eb 100644 --- a/lib/src/state_management/quran/reading/quran_reading_notifer.dart +++ b/lib/src/state_management/quran/reading/quran_reading_notifer.dart @@ -1,12 +1,15 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fpdart/fpdart.dart'; import 'package:mawaqit/src/const/constants.dart'; import 'package:mawaqit/src/domain/model/quran/moshaf_type_model.dart'; import 'package:mawaqit/src/domain/model/quran/surah_model.dart'; import 'package:mawaqit/src/domain/repository/quran/quran_reading_repository.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mawaqit/src/module/shared_preference_module.dart'; +import 'package:mawaqit/src/state_management/quran/download_quran/download_quran_notifier.dart'; +import 'package:mawaqit/src/state_management/quran/download_quran/download_quran_state.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/moshaf_type_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; @@ -15,13 +18,24 @@ import 'package:mawaqit/src/data/repository/quran/quran_reading_impl.dart'; class QuranReadingNotifier extends AutoDisposeAsyncNotifier { @override Future build() async { - final repository = ref.read(quranReadingRepositoryProvider.future); - ref.onDispose(() { - if (state.hasValue) { - state.value!.pageController.dispose(); - } - }); - return _initState(repository); + final link = ref.keepAlive(); + + try { + final repository = await ref.read(quranReadingRepositoryProvider.future); + + ref.onDispose(() { + if (state.hasValue) { + state.value!.pageController.dispose(); + } + }); + + final result = await _initState(repository); + link.close(); + return result; + } catch (e) { + link.close(); + rethrow; + } } void nextPage({bool isPortrait = false}) async { @@ -106,38 +120,47 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { ); } - Future _initState(Future repository) async { - final quranReadingRepository = await repository; + Future _initState(QuranReadingRepository repository) async { final mosqueModel = await ref.read(moshafTypeNotifierProvider.future); - return mosqueModel.selectedMoshaf.fold( - () { - throw Exception('No MoshafType'); - }, - (moshaf) async { - state = AsyncLoading(); - final svgs = await _loadSvgs(moshafType: moshaf); - final lastReadPage = await quranReadingRepository.getLastReadPage(); - final pageController = PageController(initialPage: (lastReadPage / 2).floor()); - final suwar = await getAllSuwar(); - final initialSurahName = _getCurrentSurahName(lastReadPage, suwar); - return QuranReadingState( - currentJuz: 1, - currentSurah: 1, - suwar: suwar, - currentPage: lastReadPage, - svgs: svgs, - pageController: pageController, - currentSurahName: initialSurahName, - ); - }, - ); + + try { + // Get moshaf type or set default + final moshafType = mosqueModel.selectedMoshaf.getOrElse(() => MoshafType.hafs); + + // Set moshaf type if none selected + if (mosqueModel.selectedMoshaf.isNone()) { + await ref.read(moshafTypeNotifierProvider.notifier).selectMoshafType(moshafType); + } + + state = AsyncLoading(); + final svgs = await _loadSvgs(moshafType: moshafType); + + if (svgs.isEmpty) { + throw Exception('No SVGs found for moshaf type: ${moshafType.name}'); + } + + final lastReadPage = await repository.getLastReadPage(); + final pageController = PageController(initialPage: (lastReadPage / 2).floor()); + final suwar = await getAllSuwar(); + + return QuranReadingState( + currentJuz: 1, + currentSurah: 1, + suwar: suwar, + currentPage: lastReadPage, + svgs: svgs, + pageController: pageController, + currentSurahName: _getCurrentSurahName(lastReadPage, suwar), + ); + } catch (e) { + rethrow; + } } Future _saveLastReadPage(int index) async { try { final quranRepository = await ref.read(quranReadingRepositoryProvider.future); await quranRepository.saveLastReadPage(index); - log('quran: QuranReadingNotifier: Saved last read page: $index'); } catch (e, s) { state = AsyncError(e, s); } From 243ae96677776d5ef1c9a7ea6a083f511a554fc1 Mon Sep 17 00:00:00 2001 From: Ibrahim ZEHHAF <97339607+ibrahim-zehhaf-mawaqit@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:07:20 +0100 Subject: [PATCH 02/17] Update pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f797dae85..ca29c3512 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.17.3+1 +version: 1.17.4+1 environment: From da6736a35b576c79aa6c9d6b47d1068e27ec6555 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:51:18 +0200 Subject: [PATCH 03/17] fix: Resolve overlapping and focus issues for Back and Switch buttons (#1457) --- .../quran/reading/quran_reading_screen.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index db51bcbfb..fa2ff88dc 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -136,11 +136,6 @@ class NormalViewStrategy implements QuranViewStrategy { ) { if (isPortrait) { return [ - BackButtonWidget( - isPortrait: isPortrait, - userPrefs: userPrefs, - focusNode: focusNodes.backButtonNode, - ), SurahSelectorWidget( isPortrait: isPortrait, focusNode: focusNodes.surahSelectorNode, @@ -157,15 +152,15 @@ class NormalViewStrategy implements QuranViewStrategy { focusNode: focusNodes.switchQuranNode, isThereCurrentDialogShowing: false, ), + BackButtonWidget( + isPortrait: isPortrait, + userPrefs: userPrefs, + focusNode: focusNodes.backButtonNode, + ), ]; } return [ - BackButtonWidget( - isPortrait: isPortrait, - userPrefs: userPrefs, - focusNode: focusNodes.backButtonNode, - ), _buildNavigationButtons( context, focusNodes, @@ -188,6 +183,11 @@ class NormalViewStrategy implements QuranViewStrategy { focusNode: focusNodes.switchQuranNode, isThereCurrentDialogShowing: false, ), + BackButtonWidget( + isPortrait: isPortrait, + userPrefs: userPrefs, + focusNode: focusNodes.backButtonNode, + ), ]; } From 6679d80b0ba9720db19b300d49fa01d4c640faa5 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:52:34 +0200 Subject: [PATCH 04/17] fix: Resolve pop-up issue when selecting Listening mode (#1455) - updated `_handleNavigation` method in `quran_mode_selection_screen.dart` to use `async/await` for ensuring proper completion of Quran mode selection before navigation. - fixed unexpected pop-ups by adjusting the handling of the `moshafType` state in `download_quran_popup.dart`. - improved navigation flow for both Reading and Listening modes, ensuring seamless user experience. Co-authored-by: Ghassen Ben Zahra --- .../quran/page/quran_mode_selection_screen.dart | 6 +++--- .../pages/quran/widget/download_quran_popup.dart | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/src/pages/quran/page/quran_mode_selection_screen.dart b/lib/src/pages/quran/page/quran_mode_selection_screen.dart index 093ebdec1..fe66126d5 100644 --- a/lib/src/pages/quran/page/quran_mode_selection_screen.dart +++ b/lib/src/pages/quran/page/quran_mode_selection_screen.dart @@ -80,12 +80,12 @@ class _QuranModeSelectionState extends ConsumerState { } } - void _handleNavigation(int index) { + Future _handleNavigation(int index) async { if (index == 0) { - ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.reading); + await ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.reading); Navigator.pushReplacementNamed(context, Routes.quranReading); } else { - ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); + await ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); Navigator.pushReplacementNamed(context, Routes.quranReciter); } } diff --git a/lib/src/pages/quran/widget/download_quran_popup.dart b/lib/src/pages/quran/widget/download_quran_popup.dart index 26ea12b8a..bc6db8535 100644 --- a/lib/src/pages/quran/widget/download_quran_popup.dart +++ b/lib/src/pages/quran/widget/download_quran_popup.dart @@ -125,7 +125,6 @@ class _DownloadQuranDialogState extends ConsumerState { onPressed: () async { final notifier = ref.read(downloadQuranNotifierProvider.notifier); final moshafType = ref.watch(moshafTypeNotifierProvider); - ref.read(moshafTypeNotifierProvider).maybeWhen( orElse: () {}, data: (state) async { @@ -211,7 +210,18 @@ class _DownloadQuranDialogState extends ConsumerState { actions: [ TextButton( onPressed: () { - Navigator.pop(context); + final moshafType = ref.watch(moshafTypeNotifierProvider); + moshafType.when( + data: (data) { + if (data.isFirstTime) { + Navigator.popUntil(context, (route) => route.isFirst); + } else { + Navigator.pop(context); + } + }, + error: (_, __) {}, + loading: () {}, + ); }, child: Text(S.of(context).cancel), ), From 6601d424d153bda48b7e1fe7f5ffed36fd3cbca3 Mon Sep 17 00:00:00 2001 From: Ibrahim ZEHHAF <97339607+ibrahim-zehhaf-mawaqit@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:50:34 +0100 Subject: [PATCH 05/17] Update pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index ca29c3512..26ffc2093 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.17.4+1 +version: 1.17.5+1 environment: From e2645b93fe4225c2c1d1b07c6748facf64aa2f5f Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Sun, 22 Dec 2024 23:06:19 +0100 Subject: [PATCH 06/17] fix merger conflict' --- lib/src/pages/quran/reading/quran_reading_screen.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index d861836d8..ba4e62df5 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -447,10 +447,7 @@ class _QuranReadingScreenState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) async { ref.read(downloadQuranNotifierProvider); -<<<<<<< HEAD -======= final quranReadingState = ref.watch(quranReadingNotifierProvider); ->>>>>>> 58199ed69000fdab5ad5f5305a89af16bb9a2f11 }); } From 52bdf78426e7d579619ccf99153fc839aad3e528 Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Thu, 16 Jan 2025 21:03:51 +0100 Subject: [PATCH 07/17] Fix/reboot loop onvo bug (#1527) * fix: Improve Quran Download and Navigation Experience (#1452) * fix: Ensure correct Moshaf type (Hafs) is displayed after download * fix: display Hafs Quran correctly and remove success dialog - Set Hafs as default Moshaf type if none is selected. - Auto-dismiss success dialog on download completion. - Improved state invalidation for Quran reading updates. - Added FocusNode for better dialog interaction. - Optimized resource management with keepAlive and link.close(). * fix: improve Quran Download and Navigation Experience - Redirect user to Quran reading screen automatically after successful download and extraction of Quran (Hafs). - Remove the unnecessary "OK" button to confirm Quran download completion, streamlining the user experience. - Enhance state management for download-related UI in `quran_reading_screen.dart` to handle various download states (needed, downloading, extracting). - Update `download_quran_popup.dart` to ensure proper navigation based on the user's first-time download experience. - Improve error handling and loading indicators for a smoother and more intuitive flow. * fix formating * Update pubspec.yaml * fix: Resolve overlapping and focus issues for Back and Switch buttons (#1457) * fix: Resolve pop-up issue when selecting Listening mode (#1455) - updated `_handleNavigation` method in `quran_mode_selection_screen.dart` to use `async/await` for ensuring proper completion of Quran mode selection before navigation. - fixed unexpected pop-ups by adjusting the handling of the `moshafType` state in `download_quran_popup.dart`. - improved navigation flow for both Reading and Listening modes, ensuring seamless user experience. Co-authored-by: Ghassen Ben Zahra * Update pubspec.yaml * fix merger conflict' * remove just audio package * format code --------- Co-authored-by: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Co-authored-by: Ibrahim ZEHHAF <97339607+ibrahim-zehhaf-mawaqit@users.noreply.github.com> --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 34fa522fa..28e886bc5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -160,7 +160,6 @@ dev_dependencies: flutter_lints: 3.0.1 hive_generator: ^2.0.1 mocktail: ^1.0.3 - audio_service: ^0.18.10 dart_mappable_builder: ^4.2.3 json_serializable: ^6.8.0 # For information on the generic Dart part of this file, see the From 6016b8ad37fd5da44df431267ed262cdd7406e5b Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Thu, 16 Jan 2025 22:10:46 +0100 Subject: [PATCH 08/17] Fix/schedule on off (#1535) * fix: Improve Quran Download and Navigation Experience (#1452) * fix: Ensure correct Moshaf type (Hafs) is displayed after download * fix: display Hafs Quran correctly and remove success dialog - Set Hafs as default Moshaf type if none is selected. - Auto-dismiss success dialog on download completion. - Improved state invalidation for Quran reading updates. - Added FocusNode for better dialog interaction. - Optimized resource management with keepAlive and link.close(). * fix: improve Quran Download and Navigation Experience - Redirect user to Quran reading screen automatically after successful download and extraction of Quran (Hafs). - Remove the unnecessary "OK" button to confirm Quran download completion, streamlining the user experience. - Enhance state management for download-related UI in `quran_reading_screen.dart` to handle various download states (needed, downloading, extracting). - Update `download_quran_popup.dart` to ensure proper navigation based on the user's first-time download experience. - Improve error handling and loading indicators for a smoother and more intuitive flow. * fix formating * Update pubspec.yaml * fix: Resolve overlapping and focus issues for Back and Switch buttons (#1457) * fix: Resolve pop-up issue when selecting Listening mode (#1455) - updated `_handleNavigation` method in `quran_mode_selection_screen.dart` to use `async/await` for ensuring proper completion of Quran mode selection before navigation. - fixed unexpected pop-ups by adjusting the handling of the `moshafType` state in `download_quran_popup.dart`. - improved navigation flow for both Reading and Listening modes, ensuring seamless user experience. Co-authored-by: Ghassen Ben Zahra * Update pubspec.yaml * fix merger conflict' * fix schedule save after device restart * fix reshedule to clear timers * add printstatements debug prod * remove restore on main * add delay to restore timers * remove feature from settings * remove print statements --------- Co-authored-by: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Co-authored-by: Ibrahim ZEHHAF <97339607+ibrahim-zehhaf-mawaqit@users.noreply.github.com> --- lib/main.dart | 4 + lib/src/pages/SettingScreen.dart | 19 +- lib/src/services/mosque_manager.dart | 33 +- .../toggle_screen_feature_manager.dart | 291 +++++++++++++----- .../screen_lock/screen_lock_notifier.dart | 2 +- 5 files changed, 244 insertions(+), 105 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 08c16e0e2..0676cf5fb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,6 +28,7 @@ import 'package:mawaqit/src/services/audio_manager.dart'; import 'package:mawaqit/src/services/FeatureManager.dart'; import 'package:mawaqit/src/services/mosque_manager.dart'; import 'package:mawaqit/src/services/theme_manager.dart'; +import 'package:mawaqit/src/services/toggle_screen_feature_manager.dart'; import 'package:mawaqit/src/services/user_preferences_manager.dart'; import 'package:media_kit/media_kit.dart'; import 'package:path_provider/path_provider.dart'; @@ -53,6 +54,7 @@ Future main() async { Hive.registerAdapter(ReciterModelAdapter()); Hive.registerAdapter(MoshafModelAdapter()); MediaKit.ensureInitialized(); + runApp( riverpod.ProviderScope( child: MyApp(), @@ -62,6 +64,8 @@ Future main() async { ], ), ); + await Future.delayed(const Duration(seconds: 5)); + await ToggleScreenFeature.restoreScheduledTimers(); }, ); } diff --git a/lib/src/pages/SettingScreen.dart b/lib/src/pages/SettingScreen.dart index a3a5af004..77654bd4e 100644 --- a/lib/src/pages/SettingScreen.dart +++ b/lib/src/pages/SettingScreen.dart @@ -416,17 +416,18 @@ class _SettingScreenState extends ConsumerState { style: theme.textTheme.headlineSmall, textAlign: TextAlign.center, ), - _SettingItem( - title: S.of(context).screenLock, - subtitle: S.of(context).screenLockDesc, - icon: Icon(Icons.power_settings_new, size: 35), - onTap: () => showDialog( - context: context, - builder: (context) => ScreenLockModal( - timeShiftManager: timeShiftManager, + if (timeShiftManager.isLauncherInstalled) + _SettingItem( + title: S.of(context).screenLock, + subtitle: S.of(context).screenLockDesc, + icon: Icon(Icons.power_settings_new, size: 35), + onTap: () => showDialog( + context: context, + builder: (context) => ScreenLockModal( + timeShiftManager: timeShiftManager, + ), ), ), - ), _SettingItem( title: S.of(context).appTimezone, subtitle: S.of(context).descTimezone, diff --git a/lib/src/services/mosque_manager.dart b/lib/src/services/mosque_manager.dart index ceaa0df51..0e6eff6fe 100644 --- a/lib/src/services/mosque_manager.dart +++ b/lib/src/services/mosque_manager.dart @@ -11,6 +11,7 @@ import 'package:mawaqit/src/const/constants.dart'; import 'package:mawaqit/src/helpers/Api.dart'; import 'package:mawaqit/src/helpers/PerformanceHelper.dart'; import 'package:mawaqit/src/helpers/SharedPref.dart'; +import 'package:mawaqit/src/helpers/TimeShiftManager.dart'; import 'package:mawaqit/src/mawaqit_image/mawaqit_cache.dart'; import 'package:mawaqit/src/models/mosque.dart'; import 'package:mawaqit/src/models/mosqueConfig.dart'; @@ -216,29 +217,15 @@ class MosqueManager extends ChangeNotifier with WeatherMixin, AudioMixin, Mosque (e) { times = e; final today = useTomorrowTimes ? AppDateTime.tomorrow() : AppDateTime.now(); - if (isDeviceRooted) { - if (isToggleScreenActivated) { - ToggleScreenFeature.getLastEventDate().then((lastEventDate) async { - if (lastEventDate != null && lastEventDate.day != today.day) { - isEventsSet = false; // Reset the flag if it's a new day - await ToggleScreenFeature.cancelAllScheduledTimers(); - ToggleScreenFeature.toggleFeatureState(false); - ToggleScreenFeature.checkEventsScheduled().then((_) { - if (!isEventsSet) { - ToggleScreenFeature.scheduleToggleScreen( - isIshaFajrOnly, - e.dayTimesStrings(today, salahOnly: false), - minuteBefore, - minuteAfter, - ); - ToggleScreenFeature.toggleFeatureState(true); - ToggleScreenFeature.setLastEventDate(today); - isEventsSet = true; - } - }); - } - }); - } + final timeShiftManager = TimeShiftManager(); + + if (isDeviceRooted && isToggleScreenActivated && timeShiftManager.isLauncherInstalled) { + ToggleScreenFeature.handleDailyRescheduling( + isIshaFajrOnly: isIshaFajrOnly, + timeStrings: e.dayTimesStrings(today, salahOnly: false), + minuteBefore: minuteBefore, + minuteAfter: minuteAfter, + ); } notifyListeners(); diff --git a/lib/src/services/toggle_screen_feature_manager.dart b/lib/src/services/toggle_screen_feature_manager.dart index 7a0b1b7e8..673ad4678 100644 --- a/lib/src/services/toggle_screen_feature_manager.dart +++ b/lib/src/services/toggle_screen_feature_manager.dart @@ -8,6 +8,34 @@ import 'package:mawaqit/src/helpers/AppDate.dart'; import 'package:mawaqit/src/helpers/TimeShiftManager.dart'; import 'package:shared_preferences/shared_preferences.dart'; +class TimerScheduleInfo { + final DateTime scheduledTime; + final String actionType; // 'screenOn' or 'screenOff' + final bool isFajrIsha; + + TimerScheduleInfo({ + required this.scheduledTime, + required this.actionType, + required this.isFajrIsha, + }); + + Map toJson() { + return { + 'scheduledTime': scheduledTime.toIso8601String(), + 'actionType': actionType, + 'isFajrIsha': isFajrIsha, + }; + } + + factory TimerScheduleInfo.fromJson(Map json) { + return TimerScheduleInfo( + scheduledTime: DateTime.parse(json['scheduledTime']), + actionType: json['actionType'], + isFajrIsha: json['isFajrIsha'], + ); + } +} + class ToggleScreenFeature { static final ToggleScreenFeature _instance = ToggleScreenFeature._internal(); @@ -17,88 +45,208 @@ class ToggleScreenFeature { static const String _scheduledTimersKey = TurnOnOffTvConstant.kScheduledTimersKey; static final Map> _scheduledTimers = {}; + static const String _scheduledInfoKey = 'scheduled_info_key'; + static List _scheduleInfoList = []; static Future scheduleToggleScreen( bool isfajrIshaonly, List timeStrings, int beforeDelayMinutes, int afterDelayMinutes) async { + await cancelAllScheduledTimers(); + _scheduleInfoList.clear(); + + final now = AppDateTime.now(); final timeShiftManager = TimeShiftManager(); if (isfajrIshaonly) { - String fajrTime = timeStrings[0]; - List parts = fajrTime.split(':'); - int hour = int.parse(parts[0]); - int minute = int.parse(parts[1]); - - final now = AppDateTime.now(); - DateTime scheduledDateTime = DateTime(now.year, now.month, now.day, hour, minute); + _scheduleForPrayer( + timeStrings[0], + now, + beforeDelayMinutes, + afterDelayMinutes, + true, + timeShiftManager, + ); - if (scheduledDateTime.isBefore(now)) { - scheduledDateTime = scheduledDateTime.add(Duration(days: 1)); + _scheduleForPrayer( + timeStrings[5], + now, + beforeDelayMinutes, + afterDelayMinutes, + true, + timeShiftManager, + ); + } else { + for (String timeString in timeStrings) { + _scheduleForPrayer( + timeString, + now, + beforeDelayMinutes, + afterDelayMinutes, + false, + timeShiftManager, + ); } + } - final beforeDelay = scheduledDateTime.difference(now) - Duration(minutes: beforeDelayMinutes); + await Future.wait([saveScheduledEventsToLocale(), toggleFeatureState(true), setLastEventDate(now)]); + } - if (!beforeDelay.isNegative) { - final beforeTimer = Timer(beforeDelay, () { - timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOn() : _toggleTabletScreenOn(); - }); - _scheduledTimers[fajrTime] = [beforeTimer]; - } + static Future _saveScheduleInfo() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final scheduleData = _scheduleInfoList.map((info) => info.toJson()).toList(); + await prefs.setString(_scheduledInfoKey, json.encode(scheduleData)); + } + + static Future shouldReschedule() async { + final lastEventDate = await getLastEventDate(); + final today = AppDateTime.now(); + final isFeatureActive = await getToggleFeatureState(); + final areEventsScheduled = await checkEventsScheduled(); + + final shouldReschedule = + lastEventDate != null && lastEventDate.day != today.day && isFeatureActive && !areEventsScheduled; + return shouldReschedule; + } + + static Future handleDailyRescheduling({ + required bool isIshaFajrOnly, + required List timeStrings, + required int minuteBefore, + required int minuteAfter, + }) async { + final shouldSchedule = await shouldReschedule(); + + if (shouldSchedule) { + await cancelAllScheduledTimers(); + await toggleFeatureState(false); + + await scheduleToggleScreen( + isIshaFajrOnly, + timeStrings, + minuteBefore, + minuteAfter, + ); - String ishaTime = timeStrings[5]; - parts = ishaTime.split(':'); - hour = int.parse(parts[0]); - minute = int.parse(parts[1]); + await saveScheduledEventsToLocale(); + } + } - scheduledDateTime = DateTime(now.year, now.month, now.day, hour, minute); + static Future restoreScheduledTimers() async { + try { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final scheduleDataString = prefs.getString(_scheduledInfoKey); + final isFeatureActive = await getToggleFeatureState(); - if (scheduledDateTime.isBefore(now)) { - scheduledDateTime = scheduledDateTime.add(Duration(days: 1)); + if (scheduleDataString == null || !isFeatureActive) { + return; } - final afterDelay = scheduledDateTime.difference(now) + Duration(minutes: afterDelayMinutes); + final scheduleData = json.decode(scheduleDataString) as List; + _scheduleInfoList = scheduleData + .map((data) => TimerScheduleInfo.fromJson(data)) + .where((info) => info != null) // Filter out any null entries + .toList(); - final afterTimer = Timer(afterDelay, () { - timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOff() : _toggleTabletScreenOff(); - }); - _scheduledTimers[ishaTime] = [afterTimer]; - } else { - // Original logic for all prayer times - for (String timeString in timeStrings) { - final parts = timeString.split(':'); - final hour = int.parse(parts[0]); - final minute = int.parse(parts[1]); + final now = AppDateTime.now(); + final timeShiftManager = TimeShiftManager(); - final now = AppDateTime.now(); - DateTime scheduledDateTime = DateTime(now.year, now.month, now.day, hour, minute); + _scheduleInfoList.removeWhere((info) { + final isPast = info.scheduledTime.isBefore(now); + return isPast; + }); - if (scheduledDateTime.isBefore(now)) { - scheduledDateTime = scheduledDateTime.add(Duration(days: 1)); + for (var info in _scheduleInfoList) { + try { + final delay = info.scheduledTime.difference(now); + if (delay.isNegative) { + continue; + } + + final timer = Timer(delay, () { + try { + if (info.actionType == 'screenOn') { + timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOn() : _toggleTabletScreenOn(); + } else { + timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOff() : _toggleTabletScreenOff(); + } + } catch (e) { + print('Error executing timer action: $e'); + } + }); + + final timeString = '${info.scheduledTime.hour}:${info.scheduledTime.minute}'; + _scheduledTimers[timeString] ??= []; + _scheduledTimers[timeString]!.add(timer); + } catch (e) { + print('Error scheduling timer for ${info.scheduledTime}: $e'); } + } - final beforeDelay = scheduledDateTime.difference(now) - Duration(minutes: beforeDelayMinutes); - if (beforeDelay.isNegative) { - continue; - } - final beforeTimer = Timer(beforeDelay, () { - timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOn() : _toggleTabletScreenOn(); - }); + await saveScheduledEventsToLocale(); + } catch (e) { + print('Error restoring scheduled timers: $e'); + } + } - final afterDelay = scheduledDateTime.difference(now) + Duration(minutes: afterDelayMinutes); - final afterTimer = Timer(afterDelay, () { - timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOff() : _toggleTabletScreenOff(); - }); + static void _scheduleForPrayer( + String timeString, + DateTime now, + int beforeDelayMinutes, + int afterDelayMinutes, + bool isFajrIsha, + TimeShiftManager timeShiftManager, + ) { + final parts = timeString.split(':'); + final hour = int.parse(parts[0]); + final minute = int.parse(parts[1]); + + DateTime scheduledDateTime = DateTime(now.year, now.month, now.day, hour, minute); + if (scheduledDateTime.isBefore(now)) { + scheduledDateTime = scheduledDateTime.add(Duration(days: 1)); + } - _scheduledTimers[timeString] = [beforeTimer, afterTimer]; - } + // Schedule screen on + final beforeScheduleTime = scheduledDateTime.subtract(Duration(minutes: beforeDelayMinutes)); + if (beforeScheduleTime.isAfter(now)) { + _scheduleInfoList.add(TimerScheduleInfo( + scheduledTime: beforeScheduleTime, + actionType: 'screenOn', + isFajrIsha: isFajrIsha, + )); + + final beforeTimer = Timer(beforeScheduleTime.difference(now), () { + timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOn() : _toggleTabletScreenOn(); + }); + _scheduledTimers[timeString] = [beforeTimer]; + } else { + print('Screen ON time already passed: $beforeScheduleTime'); + } + + // Schedule screen off + final afterScheduleTime = scheduledDateTime.add(Duration(minutes: afterDelayMinutes)); + _scheduleInfoList.add(TimerScheduleInfo( + scheduledTime: afterScheduleTime, + actionType: 'screenOff', + isFajrIsha: isFajrIsha, + )); + + final afterTimer = Timer(afterScheduleTime.difference(now), () { + timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOff() : _toggleTabletScreenOff(); + }); + + if (_scheduledTimers[timeString] != null) { + _scheduledTimers[timeString]!.add(afterTimer); + } else { + _scheduledTimers[timeString] = [afterTimer]; } - await _saveScheduledTimersToPrefs(); } static Future loadScheduledTimersFromPrefs() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); final timersJson = prefs.getString(_scheduledTimersKey); + if (timersJson != null) { final timersMap = json.decode(timersJson) as Map; + timersMap.forEach((timeString, timerDataList) { _scheduledTimers[timeString] = []; for (final timerData in timerDataList) { @@ -110,20 +258,6 @@ class ToggleScreenFeature { } } - static Future _saveScheduledTimersToPrefs() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - - final timersMap = _scheduledTimers.map((timeString, timers) { - return MapEntry( - timeString, - timers.map((timer) { - return {'tick': timer.tick}; - }).toList(), - ); - }); - await prefs.setString(_scheduledTimersKey, json.encode(timersMap)); - } - static Future toggleFeatureState(bool isActive) async { final SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.setBool(TurnOnOffTvConstant.kActivateToggleFeature, isActive); @@ -131,24 +265,32 @@ class ToggleScreenFeature { static Future getToggleFeatureState() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - return prefs.getBool(TurnOnOffTvConstant.kActivateToggleFeature) ?? false; + final state = prefs.getBool(TurnOnOffTvConstant.kActivateToggleFeature) ?? false; + return state; } static Future getToggleFeatureishaFajrState() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - return prefs.getBool(TurnOnOffTvConstant.kisFajrIshaOnly) ?? false; + final state = prefs.getBool(TurnOnOffTvConstant.kisFajrIshaOnly) ?? false; + return state; } static Future cancelAllScheduledTimers() async { + int canceledTimers = 0; _scheduledTimers.forEach((timeString, timers) { for (final timer in timers) { timer.cancel(); + canceledTimers++; } }); + _scheduledTimers.clear(); + _scheduleInfoList.clear(); final SharedPreferences prefs = await SharedPreferences.getInstance(); - await prefs.remove(_scheduledTimersKey); + await prefs.remove(_scheduledInfoKey); + await prefs.remove(TurnOnOffTvConstant.kScheduledTimersKey); + await prefs.setBool(TurnOnOffTvConstant.kIsEventsSet, false); } static Future _toggleBoxScreenOn() async { @@ -189,13 +331,17 @@ class ToggleScreenFeature { static Future checkEventsScheduled() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - logger.d("value${prefs.getBool("isEventsSet")}"); - return prefs.getBool(TurnOnOffTvConstant.kIsEventsSet) ?? false; + final isEventsSet = prefs.getBool(TurnOnOffTvConstant.kIsEventsSet) ?? false; + logger.d("value$isEventsSet"); + return isEventsSet; } static Future saveScheduledEventsToLocale() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); + final scheduleData = _scheduleInfoList.map((info) => info.toJson()).toList(); + await prefs.setString(_scheduledInfoKey, json.encode(scheduleData)); + final Map timersMap = {}; _scheduledTimers.forEach((key, value) { timersMap[key] = value.map((timer) { @@ -207,8 +353,9 @@ class ToggleScreenFeature { }); await prefs.setString(TurnOnOffTvConstant.kScheduledTimersKey, json.encode(timersMap)); - logger.d("Saving into local"); await prefs.setBool(TurnOnOffTvConstant.kIsEventsSet, true); + + logger.d("Saving into local"); } static Future setLastEventDate(DateTime date) async { diff --git a/lib/src/state_management/screen_lock/screen_lock_notifier.dart b/lib/src/state_management/screen_lock/screen_lock_notifier.dart index 61a0fed17..e5925b8cc 100644 --- a/lib/src/state_management/screen_lock/screen_lock_notifier.dart +++ b/lib/src/state_management/screen_lock/screen_lock_notifier.dart @@ -23,7 +23,7 @@ class ScreenLockNotifier extends AsyncNotifier { DateTime.now().add(Duration(hours: timeShiftManager.shift, minutes: timeShiftManager.shiftInMinutes)), isfajrIshaonly: prefs.getBool(TurnOnOffTvConstant.kisFajrIshaOnly) ?? false, isActive: isActive, - selectedMinuteBefore: prefs.getInt(TurnOnOffTvConstant.kMinuteBeforeKey) ?? 10, + selectedMinuteBefore: prefs.getInt(TurnOnOffTvConstant.kMinuteBeforeKey) ?? 30, selectedMinuteAfter: prefs.getInt(TurnOnOffTvConstant.kMinuteAfterKey) ?? 30, ); } From 24eeb6ad124a72252dba4835da71d694e2811812 Mon Sep 17 00:00:00 2001 From: Ibrahim ZEHHAF <97339607+ibrahim-zehhaf-mawaqit@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:07:42 +0100 Subject: [PATCH 09/17] Update pubspec.yaml --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 28e886bc5..e2aab22b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.18.2+1 +version: 1.18.3+1 environment: sdk: ">=3.0.0 <4.0.0" From 17d229dfb8c2670317ac54ae3c884d56452e76b3 Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Sun, 19 Jan 2025 23:12:16 +0100 Subject: [PATCH 10/17] change command on off screen tablet --- android/app/src/main/kotlin/com/flyweb/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/kotlin/com/flyweb/MainActivity.kt b/android/app/src/main/kotlin/com/flyweb/MainActivity.kt index 9a1b137c8..5f229523e 100644 --- a/android/app/src/main/kotlin/com/flyweb/MainActivity.kt +++ b/android/app/src/main/kotlin/com/flyweb/MainActivity.kt @@ -261,7 +261,7 @@ fun connectToNetworkWPA(call: MethodCall, result: MethodChannel.Result) { AsyncTask.execute { try { val commands = listOf( -"input keyevent 26" +"echo 0 > /sys/class/backlight/backlight/brightness" ) executeCommand(commands, result) // Lock the device } catch (e: Exception) { @@ -273,7 +273,7 @@ fun connectToNetworkWPA(call: MethodCall, result: MethodChannel.Result) { AsyncTask.execute { try { val commands = listOf( -"input keyevent 82" +"echo 110 > /sys/class/backlight/backlight/brightness" ) executeCommand(commands, result) // Lock the device } catch (e: Exception) { From b151723c30eefc745ffc199b41fbf4f20887e332 Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Mon, 20 Jan 2025 15:18:16 +0100 Subject: [PATCH 11/17] reenable screen lock feature --- lib/src/pages/SettingScreen.dart | 19 ++++--- .../quran/widget/download_quran_popup.dart | 17 ++---- lib/src/services/mosque_manager.dart | 2 +- .../quran/reading/quran_reading_notifer.dart | 54 ++++++------------- 4 files changed, 32 insertions(+), 60 deletions(-) diff --git a/lib/src/pages/SettingScreen.dart b/lib/src/pages/SettingScreen.dart index 77654bd4e..a3a5af004 100644 --- a/lib/src/pages/SettingScreen.dart +++ b/lib/src/pages/SettingScreen.dart @@ -416,18 +416,17 @@ class _SettingScreenState extends ConsumerState { style: theme.textTheme.headlineSmall, textAlign: TextAlign.center, ), - if (timeShiftManager.isLauncherInstalled) - _SettingItem( - title: S.of(context).screenLock, - subtitle: S.of(context).screenLockDesc, - icon: Icon(Icons.power_settings_new, size: 35), - onTap: () => showDialog( - context: context, - builder: (context) => ScreenLockModal( - timeShiftManager: timeShiftManager, - ), + _SettingItem( + title: S.of(context).screenLock, + subtitle: S.of(context).screenLockDesc, + icon: Icon(Icons.power_settings_new, size: 35), + onTap: () => showDialog( + context: context, + builder: (context) => ScreenLockModal( + timeShiftManager: timeShiftManager, ), ), + ), _SettingItem( title: S.of(context).appTimezone, subtitle: S.of(context).descTimezone, diff --git a/lib/src/pages/quran/widget/download_quran_popup.dart b/lib/src/pages/quran/widget/download_quran_popup.dart index c8b16905c..6dc28e5c8 100644 --- a/lib/src/pages/quran/widget/download_quran_popup.dart +++ b/lib/src/pages/quran/widget/download_quran_popup.dart @@ -87,8 +87,7 @@ class _DownloadQuranDialogState extends ConsumerState { }; } - Widget _buildUpdateAvailableDialog( - BuildContext context, UpdateAvailable state) { + Widget _buildUpdateAvailableDialog(BuildContext context, UpdateAvailable state) { final moshafName = switch (state.moshafType) { MoshafType.warsh => S.of(context).warsh, MoshafType.hafs => S.of(context).hafs, @@ -96,8 +95,7 @@ class _DownloadQuranDialogState extends ConsumerState { return AlertDialog( title: Text(S.of(context).updateAvailable), - content: Text( - S.of(context).quranUpdateDialogContent(moshafName, state.version)), + content: Text(S.of(context).quranUpdateDialogContent(moshafName, state.version)), actions: [ TextButton( onPressed: () => Navigator.pop(context), @@ -140,8 +138,7 @@ class _DownloadQuranDialogState extends ConsumerState { state.selectedMoshaf.fold(() { return null; }, (selectedMoshaf) async { - await notifier.cancelDownload( - selectedMoshaf); // Await cancellation + await notifier.cancelDownload(selectedMoshaf); // Await cancellation }); }, ); @@ -236,9 +233,7 @@ class _DownloadQuranDialogState extends ConsumerState { autofocus: true, onPressed: () async { Navigator.pop(context); - await ref - .read(downloadQuranNotifierProvider.notifier) - .downloadQuran(selectedMoshafType); + await ref.read(downloadQuranNotifierProvider.notifier).downloadQuran(selectedMoshafType); }, child: Text(S.of(context).download), ), @@ -263,9 +258,7 @@ class _DownloadQuranDialogState extends ConsumerState { setState(() { selectedMoshafType = selected!; }); - ref - .read(moshafTypeNotifierProvider.notifier) - .selectMoshafType(selectedMoshafType); + ref.read(moshafTypeNotifierProvider.notifier).selectMoshafType(selectedMoshafType); }, ); } diff --git a/lib/src/services/mosque_manager.dart b/lib/src/services/mosque_manager.dart index 0e6eff6fe..414316311 100644 --- a/lib/src/services/mosque_manager.dart +++ b/lib/src/services/mosque_manager.dart @@ -219,7 +219,7 @@ class MosqueManager extends ChangeNotifier with WeatherMixin, AudioMixin, Mosque final today = useTomorrowTimes ? AppDateTime.tomorrow() : AppDateTime.now(); final timeShiftManager = TimeShiftManager(); - if (isDeviceRooted && isToggleScreenActivated && timeShiftManager.isLauncherInstalled) { + if (isDeviceRooted && isToggleScreenActivated) { ToggleScreenFeature.handleDailyRescheduling( isIshaFajrOnly: isIshaFajrOnly, timeStrings: e.dayTimesStrings(today, salahOnly: false), diff --git a/lib/src/state_management/quran/reading/quran_reading_notifer.dart b/lib/src/state_management/quran/reading/quran_reading_notifer.dart index 0a51b4bf3..68bbb19d1 100644 --- a/lib/src/state_management/quran/reading/quran_reading_notifer.dart +++ b/lib/src/state_management/quran/reading/quran_reading_notifer.dart @@ -47,11 +47,9 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { if (nextPage < currentState.totalPages) { await _saveLastReadPage(nextPage); - currentState.pageController.nextPage( - duration: Duration(milliseconds: 300), curve: Curves.easeInOut); + currentState.pageController.nextPage(duration: Duration(milliseconds: 300), curve: Curves.easeInOut); final newSurahName = _getCurrentSurahName(nextPage, currentState.suwar); - return currentState.copyWith( - currentPage: nextPage, currentSurahName: newSurahName); + return currentState.copyWith(currentPage: nextPage, currentSurahName: newSurahName); } return currentState; }); @@ -61,16 +59,12 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { log('quran: QuranReadingNotifier: nextPage:'); state = await AsyncValue.guard(() async { final currentState = state.value!; - final previousPage = - isPortrait ? currentState.currentPage : currentState.currentPage - 2; + final previousPage = isPortrait ? currentState.currentPage : currentState.currentPage - 2; if (previousPage >= 0) { await _saveLastReadPage(previousPage); - currentState.pageController.previousPage( - duration: Duration(milliseconds: 300), curve: Curves.easeInOut); - final newSurahName = - _getCurrentSurahName(previousPage, currentState.suwar); - return currentState.copyWith( - currentPage: previousPage, currentSurahName: newSurahName); + currentState.pageController.previousPage(duration: Duration(milliseconds: 300), curve: Curves.easeInOut); + final newSurahName = _getCurrentSurahName(previousPage, currentState.suwar); + return currentState.copyWith(currentPage: previousPage, currentSurahName: newSurahName); } return currentState; }); @@ -106,11 +100,8 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { state = AsyncValue.loading(); state = await AsyncValue.guard(() async { final sharedPref = await ref.read(sharedPreferenceModule.future); - final language = - sharedPref.getString(SettingsConstant.kLanguageCode) ?? 'en'; - await ref - .read(quranNotifierProvider.notifier) - .getSuwarByLanguage(languageCode: language); + final language = sharedPref.getString(SettingsConstant.kLanguageCode) ?? 'en'; + await ref.read(quranNotifierProvider.notifier).getSuwarByLanguage(languageCode: language); return ref.read(quranNotifierProvider).maybeWhen( orElse: () { return state.value!; @@ -125,31 +116,24 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { Future> getAllSuwar() async { final sharedPref = await ref.read(sharedPreferenceModule.future); - final language = - sharedPref.getString(SettingsConstant.kLanguageCode) ?? 'en'; - await ref - .read(quranNotifierProvider.notifier) - .getSuwarByLanguage(languageCode: language); + final language = sharedPref.getString(SettingsConstant.kLanguageCode) ?? 'en'; + await ref.read(quranNotifierProvider.notifier).getSuwarByLanguage(languageCode: language); return ref.read(quranNotifierProvider).maybeWhen( orElse: () => [], data: (quranState) => quranState.suwar, ); } - Future _initState( - QuranReadingRepository repository) async { + Future _initState(QuranReadingRepository repository) async { final mosqueModel = await ref.read(moshafTypeNotifierProvider.future); try { // Get moshaf type or set default - final moshafType = - mosqueModel.selectedMoshaf.getOrElse(() => MoshafType.hafs); + final moshafType = mosqueModel.selectedMoshaf.getOrElse(() => MoshafType.hafs); // Set moshaf type if none selected if (mosqueModel.selectedMoshaf.isNone()) { - await ref - .read(moshafTypeNotifierProvider.notifier) - .selectMoshafType(moshafType); + await ref.read(moshafTypeNotifierProvider.notifier).selectMoshafType(moshafType); } state = AsyncLoading(); @@ -164,8 +148,7 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { } final lastReadPage = await repository.getLastReadPage(); - final pageController = - PageController(initialPage: (lastReadPage / 2).floor()); + final pageController = PageController(initialPage: (lastReadPage / 2).floor()); final suwar = await getAllSuwar(); return QuranReadingState( @@ -195,8 +178,7 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { Future _saveLastReadPage(int index) async { try { - final quranRepository = - await ref.read(quranReadingRepositoryProvider.future); + final quranRepository = await ref.read(quranReadingRepositoryProvider.future); await quranRepository.saveLastReadPage(index); } catch (e, s) { state = AsyncError(e, s); @@ -211,8 +193,7 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { int mid = left + (right - left) ~/ 2; if (currentPage + 1 >= suwar[mid].startPage && - (mid == suwar.length - 1 || - currentPage + 1 < suwar[mid + 1].startPage)) { + (mid == suwar.length - 1 || currentPage + 1 < suwar[mid + 1].startPage)) { return suwar[mid].name; } @@ -240,7 +221,6 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { } } -final quranReadingNotifierProvider = - AutoDisposeAsyncNotifierProvider( +final quranReadingNotifierProvider = AutoDisposeAsyncNotifierProvider( QuranReadingNotifier.new, ); From 5e6fba918577643dc3b4b923ead5a92ed3a826bb Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Thu, 23 Jan 2025 17:07:28 +0100 Subject: [PATCH 12/17] reshedule on different day --- lib/src/services/toggle_screen_feature_manager.dart | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/src/services/toggle_screen_feature_manager.dart b/lib/src/services/toggle_screen_feature_manager.dart index 673ad4678..b206dec76 100644 --- a/lib/src/services/toggle_screen_feature_manager.dart +++ b/lib/src/services/toggle_screen_feature_manager.dart @@ -90,12 +90,6 @@ class ToggleScreenFeature { await Future.wait([saveScheduledEventsToLocale(), toggleFeatureState(true), setLastEventDate(now)]); } - static Future _saveScheduleInfo() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - final scheduleData = _scheduleInfoList.map((info) => info.toJson()).toList(); - await prefs.setString(_scheduledInfoKey, json.encode(scheduleData)); - } - static Future shouldReschedule() async { final lastEventDate = await getLastEventDate(); final today = AppDateTime.now(); @@ -103,7 +97,9 @@ class ToggleScreenFeature { final areEventsScheduled = await checkEventsScheduled(); final shouldReschedule = - lastEventDate != null && lastEventDate.day != today.day && isFeatureActive && !areEventsScheduled; + lastEventDate != null && + lastEventDate.day != today.day && + isFeatureActive; return shouldReschedule; } From 16c123592d7078040b6237ffc46b6f865e1a1b2f Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Thu, 23 Jan 2025 18:10:50 +0100 Subject: [PATCH 13/17] format code --- lib/src/services/toggle_screen_feature_manager.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/services/toggle_screen_feature_manager.dart b/lib/src/services/toggle_screen_feature_manager.dart index b206dec76..90aa29500 100644 --- a/lib/src/services/toggle_screen_feature_manager.dart +++ b/lib/src/services/toggle_screen_feature_manager.dart @@ -96,10 +96,7 @@ class ToggleScreenFeature { final isFeatureActive = await getToggleFeatureState(); final areEventsScheduled = await checkEventsScheduled(); - final shouldReschedule = - lastEventDate != null && - lastEventDate.day != today.day && - isFeatureActive; + final shouldReschedule = lastEventDate != null && lastEventDate.day != today.day && isFeatureActive; return shouldReschedule; } From 1f4e88b2881039447987365230827a4867c741ab Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Thu, 23 Jan 2025 18:11:46 +0100 Subject: [PATCH 14/17] format code --- lib/src/services/toggle_screen_feature_manager.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/services/toggle_screen_feature_manager.dart b/lib/src/services/toggle_screen_feature_manager.dart index 42ecb1a3f..90aa29500 100644 --- a/lib/src/services/toggle_screen_feature_manager.dart +++ b/lib/src/services/toggle_screen_feature_manager.dart @@ -90,7 +90,6 @@ class ToggleScreenFeature { await Future.wait([saveScheduledEventsToLocale(), toggleFeatureState(true), setLastEventDate(now)]); } - static Future shouldReschedule() async { final lastEventDate = await getLastEventDate(); final today = AppDateTime.now(); @@ -101,8 +100,6 @@ class ToggleScreenFeature { return shouldReschedule; } - - static Future handleDailyRescheduling({ required bool isIshaFajrOnly, required List timeStrings, From a60a41c136d6241fce4ef37b1b1457ff67576012 Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Fri, 24 Jan 2025 14:15:46 +0100 Subject: [PATCH 15/17] reenable on/off setting for tablet --- lib/src/pages/SettingScreen.dart | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/src/pages/SettingScreen.dart b/lib/src/pages/SettingScreen.dart index 77654bd4e..a3a5af004 100644 --- a/lib/src/pages/SettingScreen.dart +++ b/lib/src/pages/SettingScreen.dart @@ -416,18 +416,17 @@ class _SettingScreenState extends ConsumerState { style: theme.textTheme.headlineSmall, textAlign: TextAlign.center, ), - if (timeShiftManager.isLauncherInstalled) - _SettingItem( - title: S.of(context).screenLock, - subtitle: S.of(context).screenLockDesc, - icon: Icon(Icons.power_settings_new, size: 35), - onTap: () => showDialog( - context: context, - builder: (context) => ScreenLockModal( - timeShiftManager: timeShiftManager, - ), + _SettingItem( + title: S.of(context).screenLock, + subtitle: S.of(context).screenLockDesc, + icon: Icon(Icons.power_settings_new, size: 35), + onTap: () => showDialog( + context: context, + builder: (context) => ScreenLockModal( + timeShiftManager: timeShiftManager, ), ), + ), _SettingItem( title: S.of(context).appTimezone, subtitle: S.of(context).descTimezone, From 0466b3a277fa28a02c9baa9a85f38efc12979f42 Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Tue, 28 Jan 2025 13:27:39 +0100 Subject: [PATCH 16/17] remove shuruk schedule timer --- .../toggle_screen_feature_manager.dart | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/lib/src/services/toggle_screen_feature_manager.dart b/lib/src/services/toggle_screen_feature_manager.dart index 90aa29500..394367270 100644 --- a/lib/src/services/toggle_screen_feature_manager.dart +++ b/lib/src/services/toggle_screen_feature_manager.dart @@ -53,12 +53,15 @@ class ToggleScreenFeature { await cancelAllScheduledTimers(); _scheduleInfoList.clear(); + // Create a copy and remove index 1 + final List prayerTimes = List.from(timeStrings); + final removedTime = prayerTimes.removeAt(1); final now = AppDateTime.now(); final timeShiftManager = TimeShiftManager(); if (isfajrIshaonly) { _scheduleForPrayer( - timeStrings[0], + prayerTimes[0], now, beforeDelayMinutes, afterDelayMinutes, @@ -67,7 +70,7 @@ class ToggleScreenFeature { ); _scheduleForPrayer( - timeStrings[5], + prayerTimes[4], now, beforeDelayMinutes, afterDelayMinutes, @@ -75,9 +78,9 @@ class ToggleScreenFeature { timeShiftManager, ); } else { - for (String timeString in timeStrings) { + for (int i = 0; i < prayerTimes.length; i++) { _scheduleForPrayer( - timeString, + prayerTimes[i], now, beforeDelayMinutes, afterDelayMinutes, @@ -120,6 +123,8 @@ class ToggleScreenFeature { ); await saveScheduledEventsToLocale(); + } else { + print('No rescheduling needed'); } } @@ -134,10 +139,8 @@ class ToggleScreenFeature { } final scheduleData = json.decode(scheduleDataString) as List; - _scheduleInfoList = scheduleData - .map((data) => TimerScheduleInfo.fromJson(data)) - .where((info) => info != null) // Filter out any null entries - .toList(); + _scheduleInfoList = + scheduleData.map((data) => TimerScheduleInfo.fromJson(data)).where((info) => info != null).toList(); final now = AppDateTime.now(); final timeShiftManager = TimeShiftManager(); @@ -197,25 +200,31 @@ class ToggleScreenFeature { scheduledDateTime = scheduledDateTime.add(Duration(days: 1)); } - // Schedule screen on final beforeScheduleTime = scheduledDateTime.subtract(Duration(minutes: beforeDelayMinutes)); + if (beforeScheduleTime.isAfter(now)) { - _scheduleInfoList.add(TimerScheduleInfo( - scheduledTime: beforeScheduleTime, - actionType: 'screenOn', - isFajrIsha: isFajrIsha, - )); - - final beforeTimer = Timer(beforeScheduleTime.difference(now), () { - timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOn() : _toggleTabletScreenOn(); - }); - _scheduledTimers[timeString] = [beforeTimer]; + // Recheck time difference right before creating Timer + final currentDiff = beforeScheduleTime.difference(AppDateTime.now()); + if (!currentDiff.isNegative) { + _scheduleInfoList.add(TimerScheduleInfo( + scheduledTime: beforeScheduleTime, + actionType: 'screenOn', + isFajrIsha: isFajrIsha, + )); + + final beforeTimer = Timer(currentDiff, () { + timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOn() : _toggleTabletScreenOn(); + }); + _scheduledTimers[timeString] = [beforeTimer]; + } else { + print('Time elapsed during scheduling for ON timer'); + } } else { print('Screen ON time already passed: $beforeScheduleTime'); } - // Schedule screen off final afterScheduleTime = scheduledDateTime.add(Duration(minutes: afterDelayMinutes)); + _scheduleInfoList.add(TimerScheduleInfo( scheduledTime: afterScheduleTime, actionType: 'screenOff', @@ -248,6 +257,8 @@ class ToggleScreenFeature { _scheduledTimers[timeString]!.add(timer); } }); + } else { + print('No stored timers found'); } } @@ -347,8 +358,6 @@ class ToggleScreenFeature { await prefs.setString(TurnOnOffTvConstant.kScheduledTimersKey, json.encode(timersMap)); await prefs.setBool(TurnOnOffTvConstant.kIsEventsSet, true); - - logger.d("Saving into local"); } static Future setLastEventDate(DateTime date) async { From f739143f94bb3af91d52af100bde206975dacbbf Mon Sep 17 00:00:00 2001 From: Ghassen Ben Zahra Date: Thu, 30 Jan 2025 16:37:22 +0100 Subject: [PATCH 17/17] remove print statements & add proper logging --- .../error/screen_on_off_exceptions.dart | 43 ++++ .../toggle_screen_feature_manager.dart | 218 ++++++++++-------- 2 files changed, 160 insertions(+), 101 deletions(-) create mode 100644 lib/src/domain/error/screen_on_off_exceptions.dart diff --git a/lib/src/domain/error/screen_on_off_exceptions.dart b/lib/src/domain/error/screen_on_off_exceptions.dart new file mode 100644 index 000000000..da8e04a35 --- /dev/null +++ b/lib/src/domain/error/screen_on_off_exceptions.dart @@ -0,0 +1,43 @@ +abstract class SchedulingException implements Exception { + final String message; + final String errorCode; + + SchedulingException(this.message, this.errorCode); + + @override + String toString() => 'Error ($errorCode): $message'; +} + +class ScheduleToggleScreenException extends SchedulingException { + ScheduleToggleScreenException(String message) + : super('Error occurred while scheduling toggle screen: $message', 'SCHEDULE_TOGGLE_SCREEN_ERROR'); +} + +class RestoreScheduledTimersException extends SchedulingException { + RestoreScheduledTimersException(String message) + : super('Error occurred while restoring scheduled timers: $message', 'RESTORE_SCHEDULED_TIMERS_ERROR'); +} + +class SchedulePrayerTimeException extends SchedulingException { + SchedulePrayerTimeException(String message) + : super('Error occurred while scheduling prayer time: $message', 'SCHEDULE_PRAYER_TIME_ERROR'); +} + +class LoadScheduledTimersException extends SchedulingException { + LoadScheduledTimersException(String message) + : super('Error occurred while loading scheduled timers: $message', 'LOAD_SCHEDULED_TIMERS_ERROR'); +} + +class DailyReschedulingException extends SchedulingException { + DailyReschedulingException(String message) + : super('Error occurred while handling daily rescheduling: $message', 'DAILY_RESCHEDULING_ERROR'); +} + +class TimerExecutionException extends SchedulingException { + TimerExecutionException(String message) + : super('Error occurred while executing timer action: $message', 'TIMER_EXECUTION_ERROR'); +} + +class UnknownSchedulingException extends SchedulingException { + UnknownSchedulingException(String message) : super('Unknown scheduling error: $message', 'UNKNOWN_SCHEDULING_ERROR'); +} diff --git a/lib/src/services/toggle_screen_feature_manager.dart b/lib/src/services/toggle_screen_feature_manager.dart index 394367270..12c3daaaf 100644 --- a/lib/src/services/toggle_screen_feature_manager.dart +++ b/lib/src/services/toggle_screen_feature_manager.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:mawaqit/main.dart'; import 'package:mawaqit/src/const/constants.dart'; +import 'package:mawaqit/src/domain/error/screen_on_off_exceptions.dart'; import 'package:mawaqit/src/helpers/AppDate.dart'; import 'package:mawaqit/src/helpers/TimeShiftManager.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -50,47 +51,51 @@ class ToggleScreenFeature { static Future scheduleToggleScreen( bool isfajrIshaonly, List timeStrings, int beforeDelayMinutes, int afterDelayMinutes) async { - await cancelAllScheduledTimers(); - _scheduleInfoList.clear(); + try { + await cancelAllScheduledTimers(); + _scheduleInfoList.clear(); + + final List prayerTimes = List.from(timeStrings); + final removedTime = prayerTimes.removeAt(1); + final now = AppDateTime.now(); + final timeShiftManager = TimeShiftManager(); + + if (isfajrIshaonly) { + _scheduleForPrayer( + prayerTimes[0], + now, + beforeDelayMinutes, + afterDelayMinutes, + true, + timeShiftManager, + ); - // Create a copy and remove index 1 - final List prayerTimes = List.from(timeStrings); - final removedTime = prayerTimes.removeAt(1); - final now = AppDateTime.now(); - final timeShiftManager = TimeShiftManager(); - - if (isfajrIshaonly) { - _scheduleForPrayer( - prayerTimes[0], - now, - beforeDelayMinutes, - afterDelayMinutes, - true, - timeShiftManager, - ); - - _scheduleForPrayer( - prayerTimes[4], - now, - beforeDelayMinutes, - afterDelayMinutes, - true, - timeShiftManager, - ); - } else { - for (int i = 0; i < prayerTimes.length; i++) { _scheduleForPrayer( - prayerTimes[i], + prayerTimes[4], now, beforeDelayMinutes, afterDelayMinutes, - false, + true, timeShiftManager, ); + } else { + for (int i = 0; i < prayerTimes.length; i++) { + _scheduleForPrayer( + prayerTimes[i], + now, + beforeDelayMinutes, + afterDelayMinutes, + false, + timeShiftManager, + ); + } } - } - await Future.wait([saveScheduledEventsToLocale(), toggleFeatureState(true), setLastEventDate(now)]); + await Future.wait([saveScheduledEventsToLocale(), toggleFeatureState(true), setLastEventDate(now)]); + } catch (e) { + logger.e('Failed to schedule toggle screen: $e'); + throw ScheduleToggleScreenException(e.toString()); + } } static Future shouldReschedule() async { @@ -109,22 +114,25 @@ class ToggleScreenFeature { required int minuteBefore, required int minuteAfter, }) async { - final shouldSchedule = await shouldReschedule(); + try { + final shouldSchedule = await shouldReschedule(); - if (shouldSchedule) { - await cancelAllScheduledTimers(); - await toggleFeatureState(false); + if (shouldSchedule) { + await cancelAllScheduledTimers(); + await toggleFeatureState(false); - await scheduleToggleScreen( - isIshaFajrOnly, - timeStrings, - minuteBefore, - minuteAfter, - ); + await scheduleToggleScreen( + isIshaFajrOnly, + timeStrings, + minuteBefore, + minuteAfter, + ); - await saveScheduledEventsToLocale(); - } else { - print('No rescheduling needed'); + await saveScheduledEventsToLocale(); + } + } catch (e) { + logger.e('Failed to handle daily rescheduling: $e'); + throw DailyReschedulingException(e.toString()); } } @@ -165,7 +173,8 @@ class ToggleScreenFeature { timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOff() : _toggleTabletScreenOff(); } } catch (e) { - print('Error executing timer action: $e'); + logger.e('Error executing timer action: $e'); + throw ScheduleToggleScreenException('Error executing timer action: $e'); } }); @@ -173,13 +182,15 @@ class ToggleScreenFeature { _scheduledTimers[timeString] ??= []; _scheduledTimers[timeString]!.add(timer); } catch (e) { - print('Error scheduling timer for ${info.scheduledTime}: $e'); + logger.e('Error scheduling timer for ${info.scheduledTime}: $e'); + throw ScheduleToggleScreenException(e.toString()); } } await saveScheduledEventsToLocale(); } catch (e) { - print('Error restoring scheduled timers: $e'); + logger.e('Error restoring scheduled timers: $e'); + throw RestoreScheduledTimersException(e.toString()); } } @@ -191,74 +202,79 @@ class ToggleScreenFeature { bool isFajrIsha, TimeShiftManager timeShiftManager, ) { - final parts = timeString.split(':'); - final hour = int.parse(parts[0]); - final minute = int.parse(parts[1]); + try { + final parts = timeString.split(':'); + final hour = int.parse(parts[0]); + final minute = int.parse(parts[1]); - DateTime scheduledDateTime = DateTime(now.year, now.month, now.day, hour, minute); - if (scheduledDateTime.isBefore(now)) { - scheduledDateTime = scheduledDateTime.add(Duration(days: 1)); - } + DateTime scheduledDateTime = DateTime(now.year, now.month, now.day, hour, minute); + if (scheduledDateTime.isBefore(now)) { + scheduledDateTime = scheduledDateTime.add(Duration(days: 1)); + } - final beforeScheduleTime = scheduledDateTime.subtract(Duration(minutes: beforeDelayMinutes)); + final beforeScheduleTime = scheduledDateTime.subtract(Duration(minutes: beforeDelayMinutes)); - if (beforeScheduleTime.isAfter(now)) { - // Recheck time difference right before creating Timer - final currentDiff = beforeScheduleTime.difference(AppDateTime.now()); - if (!currentDiff.isNegative) { - _scheduleInfoList.add(TimerScheduleInfo( - scheduledTime: beforeScheduleTime, - actionType: 'screenOn', - isFajrIsha: isFajrIsha, - )); + if (beforeScheduleTime.isAfter(now)) { + final currentDiff = beforeScheduleTime.difference(AppDateTime.now()); + if (!currentDiff.isNegative) { + _scheduleInfoList.add(TimerScheduleInfo( + scheduledTime: beforeScheduleTime, + actionType: 'screenOn', + isFajrIsha: isFajrIsha, + )); - final beforeTimer = Timer(currentDiff, () { - timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOn() : _toggleTabletScreenOn(); - }); - _scheduledTimers[timeString] = [beforeTimer]; - } else { - print('Time elapsed during scheduling for ON timer'); + final beforeTimer = Timer(currentDiff, () { + timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOn() : _toggleTabletScreenOn(); + }); + _scheduledTimers[timeString] = [beforeTimer]; + } } - } else { - print('Screen ON time already passed: $beforeScheduleTime'); - } - final afterScheduleTime = scheduledDateTime.add(Duration(minutes: afterDelayMinutes)); + final afterScheduleTime = scheduledDateTime.add(Duration(minutes: afterDelayMinutes)); - _scheduleInfoList.add(TimerScheduleInfo( - scheduledTime: afterScheduleTime, - actionType: 'screenOff', - isFajrIsha: isFajrIsha, - )); + _scheduleInfoList.add(TimerScheduleInfo( + scheduledTime: afterScheduleTime, + actionType: 'screenOff', + isFajrIsha: isFajrIsha, + )); - final afterTimer = Timer(afterScheduleTime.difference(now), () { - timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOff() : _toggleTabletScreenOff(); - }); + final afterTimer = Timer(afterScheduleTime.difference(now), () { + timeShiftManager.isLauncherInstalled ? _toggleBoxScreenOff() : _toggleTabletScreenOff(); + }); - if (_scheduledTimers[timeString] != null) { - _scheduledTimers[timeString]!.add(afterTimer); - } else { - _scheduledTimers[timeString] = [afterTimer]; + if (_scheduledTimers[timeString] != null) { + _scheduledTimers[timeString]!.add(afterTimer); + } else { + _scheduledTimers[timeString] = [afterTimer]; + } + } catch (e) { + logger.e('Error scheduling prayer time: $e'); + throw SchedulePrayerTimeException(e.toString()); } } static Future loadScheduledTimersFromPrefs() async { - final SharedPreferences prefs = await SharedPreferences.getInstance(); - final timersJson = prefs.getString(_scheduledTimersKey); + try { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final timersJson = prefs.getString(_scheduledTimersKey); - if (timersJson != null) { - final timersMap = json.decode(timersJson) as Map; + if (timersJson != null) { + final timersMap = json.decode(timersJson) as Map; - timersMap.forEach((timeString, timerDataList) { - _scheduledTimers[timeString] = []; - for (final timerData in timerDataList) { - final tick = timerData['tick'] as int; - final timer = Timer(Duration(milliseconds: tick), () {}); - _scheduledTimers[timeString]!.add(timer); - } - }); - } else { - print('No stored timers found'); + timersMap.forEach((timeString, timerDataList) { + _scheduledTimers[timeString] = []; + for (final timerData in timerDataList) { + final tick = timerData['tick'] as int; + final timer = Timer(Duration(milliseconds: tick), () {}); + _scheduledTimers[timeString]!.add(timer); + } + }); + } else { + logger.d('No stored timers found'); + } + } catch (e) { + logger.e('Error loading scheduled timers from preferences: $e'); + throw LoadScheduledTimersException(e.toString()); } }