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 093ebdec..fe66126d 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/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 74fda1ae..fa2ff88d 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, + ), ]; } @@ -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 a1e0db5c..bc6db853 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,67 @@ 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)}%'), + ], ), - ], - ); - } - - 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)}%'), + actions: [ + TextButton( + autofocus: true, + onPressed: () async { + final notifier = ref.read(downloadQuranNotifierProvider.notifier); + final moshafType = ref.watch(moshafTypeNotifierProvider); + 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 +184,57 @@ 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: () { + 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), ), - _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 +243,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 +281,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 74d22929..46e53536 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 d6fd02e2..b23bcc3e 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); }