From 34150d9868486946112525a83407c62f115ea5ea Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:39:22 +0300 Subject: [PATCH 01/33] feat(auto_reading): add state management for auto reading feature - Implement `AutoScrollState` to handle auto scroll speed, visibility, and font size settings. - Add `AutoScrollNotifier` to manage auto-scrolling functionality with start, stop, and speed control. - Include derived properties for controlling the visibility of speed control and scroll behavior. - Support toggling between single-page view and auto-scrolling. --- .../auto_reading/auto_reading_notifier.dart | 75 +++++++++++++++++++ .../auto_reading/auto_reading_state.dart | 35 +++++++++ 2 files changed, 110 insertions(+) create mode 100644 lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart create mode 100644 lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart new file mode 100644 index 000000000..20ff5c5b7 --- /dev/null +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart @@ -0,0 +1,75 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_state.dart'; + +class AutoScrollNotifier extends Notifier { + Timer? _autoScrollTimer; + Timer? _hideTimer; + late final ScrollController scrollController; + + @override + AutoScrollState build() { + scrollController = ScrollController(); + ref.onDispose(() { + _autoScrollTimer?.cancel(); + _hideTimer?.cancel(); + scrollController.dispose(); + }); + return AutoScrollState(); + } + + void toggleAutoScroll() { + state = state.copyWith( + isSinglePageView: !state.isSinglePageView, + ); + + if (state.isAutoScrolling) { + startAutoScroll(); + } else { + stopAutoScroll(); + } + } + + void startAutoScroll() { + _autoScrollTimer?.cancel(); + _autoScrollTimer = Timer.periodic(Duration(milliseconds: 50), (timer) { + if (scrollController.position.pixels < scrollController.position.maxScrollExtent) { + scrollController.jumpTo(scrollController.position.pixels + (state.autoScrollSpeed * 2)); + } else { + stopAutoScroll(); + } + }); + } + + void stopAutoScroll() { + _autoScrollTimer?.cancel(); + _autoScrollTimer = null; + } + + void changeSpeed(double newSpeed) { + state = state.copyWith(autoScrollSpeed: newSpeed.clamp(0.1, 5.0)); + if (state.isAutoScrolling) { + startAutoScroll(); + } + } + + void showControls() { + state = state.copyWith(isVisible: true); + startHideTimer(); + } + + void startHideTimer() { + _hideTimer?.cancel(); + _hideTimer = Timer(Duration(seconds: 12), () { + state = state.copyWith(isVisible: false); + }); + } + + void changeFontSize() { + double newFontSize = state.fontSize + 0.2; + if (newFontSize > state.maxFontSize) newFontSize = 1.0; + state = state.copyWith(fontSize: newFontSize); + } +} diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart new file mode 100644 index 000000000..58bff39bc --- /dev/null +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart @@ -0,0 +1,35 @@ +class AutoScrollState { + final bool isSinglePageView; + final double autoScrollSpeed; + final bool isVisible; + final double fontSize; + final double maxFontSize; + + AutoScrollState({ + this.isSinglePageView = false, + this.autoScrollSpeed = 1.0, + this.isVisible = true, + this.fontSize = 1.0, + this.maxFontSize = 3.0, + }); + + // Derived properties + bool get isAutoScrolling => !isSinglePageView; + bool get showSpeedControl => !isSinglePageView; + + AutoScrollState copyWith({ + bool? isSinglePageView, + double? autoScrollSpeed, + bool? isVisible, + double? fontSize, + double? maxFontSize, + }) { + return AutoScrollState( + isSinglePageView: isSinglePageView ?? this.isSinglePageView, + autoScrollSpeed: autoScrollSpeed ?? this.autoScrollSpeed, + isVisible: isVisible ?? this.isVisible, + fontSize: fontSize ?? this.fontSize, + maxFontSize: maxFontSize ?? this.maxFontSize, + ); + } +} From ea306d7ddebf922a38f4ef13692940ae337a8ab9 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:40:49 +0300 Subject: [PATCH 02/33] feat(quran): add play toggle button and refactor directory structure - Add play toggle button to `QuranReadingScreen` with portrait and landscape support. - Move `quran_reading_screen.dart` to new `reading` directory for better organization. - Create `QuranFloatingActionButtons` widget for handling floating action buttons in portrait and landscape modes. --- .../page/quran_mode_selection_screen.dart | 2 +- .../quran/page/reciter_selection_screen.dart | 2 +- .../quran_reading_screen.dart | 30 +++++- .../widget/quran_floating_action_buttons.dart | 93 +++++++++++++++++++ .../pages/quran/widget/quran_background.dart | 2 +- lib/src/widgets/MawaqitDrawer.dart | 2 +- 6 files changed, 126 insertions(+), 5 deletions(-) rename lib/src/pages/quran/{page => reading}/quran_reading_screen.dart (93%) create mode 100644 lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart 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 79069e79b..fc8fd32d4 100644 --- a/lib/src/pages/quran/page/quran_mode_selection_screen.dart +++ b/lib/src/pages/quran/page/quran_mode_selection_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mawaqit/i18n/l10n.dart'; -import 'package:mawaqit/src/pages/quran/page/quran_reading_screen.dart'; +import 'package:mawaqit/src/pages/quran/reading/quran_reading_screen.dart'; import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; import 'package:mawaqit/src/pages/quran/widget/quran_background.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; diff --git a/lib/src/pages/quran/page/reciter_selection_screen.dart b/lib/src/pages/quran/page/reciter_selection_screen.dart index abe178954..60eb2407b 100644 --- a/lib/src/pages/quran/page/reciter_selection_screen.dart +++ b/lib/src/pages/quran/page/reciter_selection_screen.dart @@ -7,7 +7,7 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:mawaqit/const/resource.dart'; -import 'package:mawaqit/src/pages/quran/page/quran_reading_screen.dart'; +import 'package:mawaqit/src/pages/quran/reading/quran_reading_screen.dart'; import 'package:mawaqit/src/pages/quran/widget/recite_type_grid_view.dart'; import 'package:mawaqit/src/services/theme_manager.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; diff --git a/lib/src/pages/quran/page/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart similarity index 93% rename from lib/src/pages/quran/page/quran_reading_screen.dart rename to lib/src/pages/quran/reading/quran_reading_screen.dart index b2dbe819d..e7863101a 100644 --- a/lib/src/pages/quran/page/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -7,6 +7,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:mawaqit/i18n/l10n.dart'; import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; +import 'package:mawaqit/src/pages/quran/reading/widget/quran_floating_action_buttons.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_reading_widgets.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_surah_selector.dart'; @@ -40,6 +41,7 @@ class _QuranReadingScreenState extends ConsumerState { late FocusNode _switchQuranFocusNode; late FocusNode _switchQuranModeNode; late FocusNode _switchScreenViewFocusNode; + late FocusNode _switchToPlayQuranFocusNode; late FocusNode _portraitModeBackButtonFocusNode; late FocusNode _portraitModeSwitchQuranFocusNode; late FocusNode _portraitModePageSelectorFocusNode; @@ -68,6 +70,7 @@ class _QuranReadingScreenState extends ConsumerState { _portraitModeBackButtonFocusNode = FocusNode(debugLabel: 'portrait_mode_back_button_node'); _portraitModeSwitchQuranFocusNode = FocusNode(debugLabel: 'portrait_mode_switch_quran_node'); _portraitModePageSelectorFocusNode = FocusNode(debugLabel: 'portrait_mode_page_selector_node'); + _switchToPlayQuranFocusNode = FocusNode(debugLabel: 'switch_to_play_quran_node'); } @override @@ -85,6 +88,7 @@ class _QuranReadingScreenState extends ConsumerState { _portraitModeBackButtonFocusNode.dispose(); _portraitModeSwitchQuranFocusNode.dispose(); _portraitModePageSelectorFocusNode.dispose(); + _switchToPlayQuranFocusNode.dispose(); } void _toggleOrientation(UserPreferencesManager userPrefs) { @@ -238,6 +242,8 @@ class _QuranReadingScreenState extends ConsumerState { _buildOrientationToggleButton(isPortrait, userPrefs), SizedBox(width: 200.sp), _buildQuranModeButton(isPortrait, userPrefs, context), + SizedBox(width: 200.sp), + _buildPlayToggleButton(isPortrait), ], ); } @@ -246,8 +252,10 @@ class _QuranReadingScreenState extends ConsumerState { return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ + _buildPlayToggleButton(isPortrait), + SizedBox(height: 1.h), _buildOrientationToggleButton(isPortrait, userPrefs), - SizedBox(height: 10), + SizedBox(height: 1.h), _buildQuranModeButton(isPortrait, userPrefs, context), ], ); @@ -300,6 +308,26 @@ class _QuranReadingScreenState extends ConsumerState { ); } + Widget _buildPlayToggleButton(bool isPortrait) { + return SizedBox( + width: isPortrait ? 35.sp : 30.sp, + height: isPortrait ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: _switchToPlayQuranFocusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + !isPortrait ? Icons.play_arrow : Icons.stay_current_landscape, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, + ), + onPressed: () { + ; + }, + heroTag: null, + ), + ); + } + void _scrollPageList(ScrollDirection direction) { final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; if (direction == ScrollDirection.forward) { diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart new file mode 100644 index 000000000..f3d0a09b8 --- /dev/null +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; + +import 'package:mawaqit/src/services/user_preferences_manager.dart'; +import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; +import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; + +class QuranFloatingActionButtons extends ConsumerWidget { + final UserPreferencesManager userPrefs; + final FocusNode switchScreenViewFocusNode; + final FocusNode switchQuranModeNode; + + const QuranFloatingActionButtons({ + super.key, + required this.userPrefs, + required this.switchScreenViewFocusNode, + required this.switchQuranModeNode, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return OrientationBuilder( + builder: (context, orientation) { + final isPortrait = orientation == Orientation.portrait; + return isPortrait ? _buildPortraitButtons(context, ref) : _buildLandscapeButtons(context, ref); + }, + ); + } + + Widget _buildPortraitButtons(BuildContext context, WidgetRef ref) { + return Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FloatingActionButton( + heroTag: "switchScreenView", + focusNode: switchScreenViewFocusNode, + onPressed: () { + userPrefs.orientationLandscape = !userPrefs.orientationLandscape; + }, + child: const Icon(Icons.screen_rotation), + ), + const SizedBox(height: 16), + FloatingActionButton( + heroTag: "switchQuranMode", + focusNode: switchQuranModeNode, + onPressed: () { + ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => ReciterSelectionScreen.withoutSurahName(), + ), + ); + }, + child: const Icon(Icons.menu_book), + ), + ], + ); + } + + Widget _buildLandscapeButtons(BuildContext context, WidgetRef ref) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + FloatingActionButton( + heroTag: "switchScreenView", + focusNode: switchScreenViewFocusNode, + onPressed: () { + userPrefs.orientationLandscape = !userPrefs.orientationLandscape; + }, + child: const Icon(Icons.screen_rotation), + ), + const SizedBox(width: 16), + FloatingActionButton( + heroTag: "switchQuranMode", + focusNode: switchQuranModeNode, + onPressed: () { + ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => ReciterSelectionScreen.withoutSurahName(), + ), + ); + }, + child: const Icon(Icons.menu_book), + ), + ], + ); + } +} diff --git a/lib/src/pages/quran/widget/quran_background.dart b/lib/src/pages/quran/widget/quran_background.dart index 31b4618fb..d991af318 100644 --- a/lib/src/pages/quran/widget/quran_background.dart +++ b/lib/src/pages/quran/widget/quran_background.dart @@ -3,7 +3,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mawaqit/const/resource.dart'; -import 'package:mawaqit/src/pages/quran/page/quran_reading_screen.dart'; +import 'package:mawaqit/src/pages/quran/reading/quran_reading_screen.dart'; import 'package:mawaqit/src/services/theme_manager.dart'; import 'package:sizer/sizer.dart'; diff --git a/lib/src/widgets/MawaqitDrawer.dart b/lib/src/widgets/MawaqitDrawer.dart index bfd7fddc9..ae4fb65d3 100644 --- a/lib/src/widgets/MawaqitDrawer.dart +++ b/lib/src/widgets/MawaqitDrawer.dart @@ -32,7 +32,7 @@ import 'package:mawaqit/src/pages/SettingScreen.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import '../pages/quran/page/quran_mode_selection_screen.dart'; -import '../pages/quran/page/quran_reading_screen.dart'; +import 'package:mawaqit/src/pages/quran/reading/quran_reading_screen.dart'; import '../state_management/quran/quran/quran_state.dart'; class MawaqitDrawer extends ConsumerWidget { From caffbc26f2b5e1a1ce842878a128230f1fefdc91 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Mon, 21 Oct 2024 21:59:21 +0300 Subject: [PATCH 03/33] refactor: Extract floating action controls into new widget with passed focus nodes - Extracted floating action controls into `QuranFloatingActionControls` widget. - Used `OrientationBuilder` within the new widget to determine orientation internally. - Passed focus nodes from `QuranReadingScreen` to the new widget for external focus management. - Maintained existing UI and design without modifications. --- .../quran/reading/quran_reading_screen.dart | 33 ++-- .../widget/quran_floating_action_buttons.dart | 162 ++++++++++++------ 2 files changed, 126 insertions(+), 69 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index e7863101a..41c3fef73 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -149,11 +149,12 @@ class _QuranReadingScreenState extends ConsumerState { }, child: Scaffold( backgroundColor: Colors.white, - floatingActionButtonLocation: - isPortrait ? FloatingActionButtonLocation.startFloat : _getFloatingActionButtonLocation(context), - floatingActionButton: isPortrait - ? buildFloatingPortrait(isPortrait, userPrefs, context) - : buildFloatingLandscape(isPortrait, userPrefs, context), + floatingActionButtonLocation: _getFloatingActionButtonLocation(context), + floatingActionButton: QuranFloatingActionControls( + switchScreenViewFocusNode: _switchScreenViewFocusNode, + switchQuranModeNode: _switchQuranModeNode, + switchToPlayQuranFocusNode: _switchToPlayQuranFocusNode, + ), body: _buildBody(quranReadingState, isPortrait, userPrefs), ), ); @@ -366,13 +367,20 @@ class _QuranReadingScreenState extends ConsumerState { FloatingActionButtonLocation _getFloatingActionButtonLocation(BuildContext context) { final TextDirection textDirection = Directionality.of(context); - switch (textDirection) { - case TextDirection.ltr: - return FloatingActionButtonLocation.endFloat; - case TextDirection.rtl: - return FloatingActionButtonLocation.startFloat; - default: - return FloatingActionButtonLocation.endFloat; + final orientation = MediaQuery.of(context).orientation; + final isPortrait = orientation == Orientation.portrait; + + if (isPortrait) { + return FloatingActionButtonLocation.startFloat; + } else { + switch (textDirection) { + case TextDirection.ltr: + return FloatingActionButtonLocation.endFloat; + case TextDirection.rtl: + return FloatingActionButtonLocation.startFloat; + default: + return FloatingActionButtonLocation.endFloat; + } } } @@ -419,3 +427,4 @@ class _QuranReadingScreenState extends ConsumerState { bool _isThereCurrentDialogShowing(BuildContext context) => ModalRoute.of(context)?.isCurrent != true; } + diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index f3d0a09b8..088790dcc 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -1,93 +1,141 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; - import 'package:mawaqit/src/services/user_preferences_manager.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; +import 'package:provider/provider.dart'; +import 'package:sizer/sizer.dart'; -class QuranFloatingActionButtons extends ConsumerWidget { - final UserPreferencesManager userPrefs; +class QuranFloatingActionControls extends ConsumerWidget { final FocusNode switchScreenViewFocusNode; final FocusNode switchQuranModeNode; + final FocusNode switchToPlayQuranFocusNode; - const QuranFloatingActionButtons({ + const QuranFloatingActionControls({ super.key, - required this.userPrefs, required this.switchScreenViewFocusNode, required this.switchQuranModeNode, + required this.switchToPlayQuranFocusNode, }); @override Widget build(BuildContext context, WidgetRef ref) { + final userPrefs = context.watch(); + return OrientationBuilder( builder: (context, orientation) { final isPortrait = orientation == Orientation.portrait; - return isPortrait ? _buildPortraitButtons(context, ref) : _buildLandscapeButtons(context, ref); + + return isPortrait + ? _buildFloatingPortrait(userPrefs, isPortrait, ref, context) + : _buildFloatingLandscape(userPrefs, isPortrait, ref, context); }, ); } - Widget _buildPortraitButtons(BuildContext context, WidgetRef ref) { + Widget _buildFloatingPortrait( + UserPreferencesManager userPrefs, bool isPortrait, WidgetRef ref, BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _buildOrientationToggleButton(userPrefs, isPortrait, context), + SizedBox(width: 200.sp), + _buildQuranModeButton(userPrefs, isPortrait, ref, context), + SizedBox(width: 200.sp), + _buildPlayToggleButton(isPortrait), + ], + ); + } + + Widget _buildFloatingLandscape( + UserPreferencesManager userPrefs, bool isPortrait, WidgetRef ref, BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, children: [ - FloatingActionButton( - heroTag: "switchScreenView", - focusNode: switchScreenViewFocusNode, - onPressed: () { - userPrefs.orientationLandscape = !userPrefs.orientationLandscape; - }, - child: const Icon(Icons.screen_rotation), - ), - const SizedBox(height: 16), - FloatingActionButton( - heroTag: "switchQuranMode", - focusNode: switchQuranModeNode, - onPressed: () { - ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => ReciterSelectionScreen.withoutSurahName(), - ), - ); - }, - child: const Icon(Icons.menu_book), - ), + _buildPlayToggleButton(isPortrait), + SizedBox(height: 1.h), + _buildOrientationToggleButton(userPrefs, isPortrait, context), + SizedBox(height: 1.h), + _buildQuranModeButton(userPrefs, isPortrait, ref, context), ], ); } - Widget _buildLandscapeButtons(BuildContext context, WidgetRef ref) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FloatingActionButton( - heroTag: "switchScreenView", - focusNode: switchScreenViewFocusNode, - onPressed: () { - userPrefs.orientationLandscape = !userPrefs.orientationLandscape; - }, - child: const Icon(Icons.screen_rotation), + Widget _buildOrientationToggleButton( + UserPreferencesManager userPrefs, bool isPortrait, BuildContext context) { + return SizedBox( + width: isPortrait ? 35.sp : 30.sp, + height: isPortrait ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: switchScreenViewFocusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + !isPortrait ? Icons.stay_current_portrait : Icons.stay_current_landscape, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, ), - const SizedBox(width: 16), - FloatingActionButton( - heroTag: "switchQuranMode", - focusNode: switchQuranModeNode, - onPressed: () { - ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => ReciterSelectionScreen.withoutSurahName(), - ), - ); - }, - child: const Icon(Icons.menu_book), + onPressed: () => _toggleOrientation(userPrefs, context), + heroTag: null, + ), + ); + } + + void _toggleOrientation(UserPreferencesManager userPrefs, BuildContext context) { + final newOrientation = MediaQuery.of(context).orientation == Orientation.portrait + ? Orientation.landscape + : Orientation.portrait; + + userPrefs.orientationLandscape = newOrientation == Orientation.landscape; + } + + Widget _buildQuranModeButton( + UserPreferencesManager userPrefs, bool isPortrait, WidgetRef ref, BuildContext context) { + return SizedBox( + width: isPortrait ? 35.sp : 30.sp, + height: isPortrait ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: switchQuranModeNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + Icons.headset, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, ), - ], + onPressed: () async { + ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); + if (isPortrait) { + userPrefs.orientationLandscape = true; + } + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => ReciterSelectionScreen.withoutSurahName(), + ), + ); + }, + heroTag: null, + ), + ); + } + + Widget _buildPlayToggleButton(bool isPortrait) { + return SizedBox( + width: isPortrait ? 35.sp : 30.sp, + height: isPortrait ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: switchToPlayQuranFocusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + !isPortrait ? Icons.play_arrow : Icons.stay_current_landscape, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, + ), + onPressed: () { + // Implement play functionality + }, + heroTag: null, + ), ); } } From 9426c2a97f06c6874dda329ed0c69a9a149f06b4 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:23:17 +0300 Subject: [PATCH 04/33] feat: add the to_string and making the AutoScrollNotifier auto disposed --- .../auto_reading/auto_reading_notifier.dart | 9 ++++++++- .../reading/auto_reading/auto_reading_state.dart | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart index 20ff5c5b7..4f5fe13c0 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart @@ -4,7 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_state.dart'; -class AutoScrollNotifier extends Notifier { +class AutoScrollNotifier extends AutoDisposeNotifier { Timer? _autoScrollTimer; Timer? _hideTimer; late final ScrollController scrollController; @@ -34,6 +34,9 @@ class AutoScrollNotifier extends Notifier { void startAutoScroll() { _autoScrollTimer?.cancel(); + state = state.copyWith( + isSinglePageView: true, + ); _autoScrollTimer = Timer.periodic(Duration(milliseconds: 50), (timer) { if (scrollController.position.pixels < scrollController.position.maxScrollExtent) { scrollController.jumpTo(scrollController.position.pixels + (state.autoScrollSpeed * 2)); @@ -73,3 +76,7 @@ class AutoScrollNotifier extends Notifier { state = state.copyWith(fontSize: newFontSize); } } + +final autoScrollNotifierProvider = AutoDisposeNotifierProvider( + AutoScrollNotifier.new, +); diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart index 58bff39bc..a3c3d855c 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart @@ -32,4 +32,18 @@ class AutoScrollState { maxFontSize: maxFontSize ?? this.maxFontSize, ); } + + @override + String toString() { + return 'AutoScrollState(' + 'isSinglePageView: $isSinglePageView, ' + 'autoScrollSpeed: $autoScrollSpeed, ' + 'isVisible: $isVisible, ' + 'fontSize: $fontSize, ' + 'maxFontSize: $maxFontSize, ' + 'isAutoScrolling: $isAutoScrolling, ' + 'showSpeedControl: $showSpeedControl' + ')'; + } + } From 6417589af2a79d29cb94696bba3e1cc0ccc53de4 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:24:09 +0300 Subject: [PATCH 05/33] modify the new ui --- .../widget/quran_floating_action_buttons.dart | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 088790dcc..b0e0457ed 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -4,6 +4,7 @@ import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; import 'package:mawaqit/src/services/user_preferences_manager.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; +import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart'; import 'package:provider/provider.dart'; import 'package:sizer/sizer.dart'; @@ -43,7 +44,7 @@ class QuranFloatingActionControls extends ConsumerWidget { SizedBox(width: 200.sp), _buildQuranModeButton(userPrefs, isPortrait, ref, context), SizedBox(width: 200.sp), - _buildPlayToggleButton(isPortrait), + _buildPlayToggleButton(isPortrait, ref), ], ); } @@ -53,7 +54,7 @@ class QuranFloatingActionControls extends ConsumerWidget { return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - _buildPlayToggleButton(isPortrait), + _buildPlayToggleButton(isPortrait, ref), SizedBox(height: 1.h), _buildOrientationToggleButton(userPrefs, isPortrait, context), SizedBox(height: 1.h), @@ -62,8 +63,7 @@ class QuranFloatingActionControls extends ConsumerWidget { ); } - Widget _buildOrientationToggleButton( - UserPreferencesManager userPrefs, bool isPortrait, BuildContext context) { + Widget _buildOrientationToggleButton(UserPreferencesManager userPrefs, bool isPortrait, BuildContext context) { return SizedBox( width: isPortrait ? 35.sp : 30.sp, height: isPortrait ? 35.sp : 30.sp, @@ -82,15 +82,13 @@ class QuranFloatingActionControls extends ConsumerWidget { } void _toggleOrientation(UserPreferencesManager userPrefs, BuildContext context) { - final newOrientation = MediaQuery.of(context).orientation == Orientation.portrait - ? Orientation.landscape - : Orientation.portrait; + final newOrientation = + MediaQuery.of(context).orientation == Orientation.portrait ? Orientation.landscape : Orientation.portrait; userPrefs.orientationLandscape = newOrientation == Orientation.landscape; } - Widget _buildQuranModeButton( - UserPreferencesManager userPrefs, bool isPortrait, WidgetRef ref, BuildContext context) { + Widget _buildQuranModeButton(UserPreferencesManager userPrefs, bool isPortrait, WidgetRef ref, BuildContext context) { return SizedBox( width: isPortrait ? 35.sp : 30.sp, height: isPortrait ? 35.sp : 30.sp, @@ -119,7 +117,9 @@ class QuranFloatingActionControls extends ConsumerWidget { ); } - Widget _buildPlayToggleButton(bool isPortrait) { + Widget _buildPlayToggleButton(bool isPortrait, WidgetRef ref) { + final autoScrollState = ref.watch(autoScrollNotifierProvider); + return SizedBox( width: isPortrait ? 35.sp : 30.sp, height: isPortrait ? 35.sp : 30.sp, @@ -127,12 +127,12 @@ class QuranFloatingActionControls extends ConsumerWidget { focusNode: switchToPlayQuranFocusNode, backgroundColor: Colors.black.withOpacity(.3), child: Icon( - !isPortrait ? Icons.play_arrow : Icons.stay_current_landscape, + autoScrollState.isAutoScrolling ? Icons.play_arrow : Icons.pause, color: Colors.white, size: isPortrait ? 20.sp : 15.sp, ), onPressed: () { - // Implement play functionality + ref.read(autoScrollNotifierProvider.notifier).startAutoScroll(); }, heroTag: null, ), From 0f4d74781792035518716cb30d4f7e7c317ca78f Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Mon, 21 Oct 2024 22:57:44 +0300 Subject: [PATCH 06/33] feat: add auto-scrolling reading mode with font size and speed controls - QuranFloatingActionControls: - Implemented `_buildAutoScrollingReadingMode` to display controls when auto-scroll is active. - Added methods: - `_buildFontSizeControls` for adjusting font size. - `_buildSpeedControls` for adjusting auto-scroll speed. - `_buildPlayPauseButton` for toggling auto-scroll. - `_buildActionButton` as a helper for creating action buttons. - Modified `_buildFloatingPortrait` and `_buildFloatingLandscape` to display auto-scroll controls based on the current state. - AutoScrollState: - Fixed `isAutoScrolling` getter to correctly represent the auto-scrolling state. - AutoScrollNotifier: - Added methods: - `increaseFontSize` and `decreaseFontSize` to adjust font size. - `increaseSpeed` and `decreaseSpeed` to adjust auto-scroll speed. - Updated `startAutoScroll` to use dynamic speed settings. --- .../widget/quran_floating_action_buttons.dart | 110 +++++++++++++++--- .../auto_reading/auto_reading_notifier.dart | 30 +++++ .../auto_reading/auto_reading_state.dart | 2 +- 3 files changed, 128 insertions(+), 14 deletions(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index b0e0457ed..7b49a6bb3 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -5,6 +5,7 @@ import 'package:mawaqit/src/services/user_preferences_manager.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart'; +import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_state.dart'; import 'package:provider/provider.dart'; import 'package:sizer/sizer.dart'; @@ -27,16 +28,26 @@ class QuranFloatingActionControls extends ConsumerWidget { return OrientationBuilder( builder: (context, orientation) { final isPortrait = orientation == Orientation.portrait; + final autoScrollState = ref.watch(autoScrollNotifierProvider); - return isPortrait - ? _buildFloatingPortrait(userPrefs, isPortrait, ref, context) - : _buildFloatingLandscape(userPrefs, isPortrait, ref, context); + if (autoScrollState.isAutoScrolling) { + return _buildAutoScrollingReadingMode(isPortrait, ref); + } else { + return isPortrait + ? _buildFloatingPortrait(userPrefs, isPortrait, ref, context, autoScrollState) + : _buildFloatingLandscape(userPrefs, isPortrait, ref, context, autoScrollState); + } }, ); } Widget _buildFloatingPortrait( - UserPreferencesManager userPrefs, bool isPortrait, WidgetRef ref, BuildContext context) { + UserPreferencesManager userPrefs, + bool isPortrait, + WidgetRef ref, + BuildContext context, + AutoScrollState autoScrollState, + ) { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -44,17 +55,26 @@ class QuranFloatingActionControls extends ConsumerWidget { SizedBox(width: 200.sp), _buildQuranModeButton(userPrefs, isPortrait, ref, context), SizedBox(width: 200.sp), - _buildPlayToggleButton(isPortrait, ref), + _buildPlayPauseButton( + isPortrait, + ref, + autoScrollState + ), ], ); } Widget _buildFloatingLandscape( - UserPreferencesManager userPrefs, bool isPortrait, WidgetRef ref, BuildContext context) { + UserPreferencesManager userPrefs, + bool isPortrait, + WidgetRef ref, + BuildContext context, + AutoScrollState autoScrollState, + ) { return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - _buildPlayToggleButton(isPortrait, ref), + _buildPlayPauseButton(isPortrait, ref, autoScrollState), SizedBox(height: 1.h), _buildOrientationToggleButton(userPrefs, isPortrait, context), SizedBox(height: 1.h), @@ -117,24 +137,88 @@ class QuranFloatingActionControls extends ConsumerWidget { ); } - Widget _buildPlayToggleButton(bool isPortrait, WidgetRef ref) { + Widget _buildAutoScrollingReadingMode(bool isPortrait, WidgetRef ref) { final autoScrollState = ref.watch(autoScrollNotifierProvider); + return Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _buildFontSizeControls(isPortrait, ref), + SizedBox(height: 1.h), + _buildSpeedControls(isPortrait, ref), + SizedBox(height: 1.h), + _buildPlayPauseButton(isPortrait, ref, autoScrollState), + ], + ); + } + + Widget _buildFontSizeControls(bool isPortrait, WidgetRef ref) { + return Column( + children: [ + _buildActionButton( + isPortrait, + icon: Icons.remove, + onPressed: () => ref.read(autoScrollNotifierProvider.notifier).decreaseFontSize(), + tooltip: 'Decrease Font Size', + ), + SizedBox(width: 1.h), + _buildActionButton( + isPortrait, + icon: Icons.add, + onPressed: () => ref.read(autoScrollNotifierProvider.notifier).increaseFontSize(), + tooltip: 'Increase Font Size', + ), + ], + ); + } + + Widget _buildSpeedControls(bool isPortrait, WidgetRef ref) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildActionButton( + isPortrait, + icon: Icons.fast_rewind, + onPressed: () => ref.read(autoScrollNotifierProvider.notifier).decreaseSpeed(), + tooltip: 'Decrease Speed', + ), + SizedBox(width: 2.h), + _buildActionButton( + isPortrait, + icon: Icons.fast_forward, + onPressed: () => ref.read(autoScrollNotifierProvider.notifier).increaseSpeed(), + tooltip: 'Increase Speed', + ), + ], + ); + } + + Widget _buildPlayPauseButton(bool isPortrait, WidgetRef ref, AutoScrollState autoScrollState) { + return _buildActionButton( + isPortrait, + icon: autoScrollState.isAutoScrolling ? Icons.pause : Icons.play_arrow, + onPressed: () { + ref.read(autoScrollNotifierProvider.notifier).toggleAutoScroll(); + }, + tooltip: autoScrollState.isAutoScrolling ? 'Pause' : 'Play', + ); + } + + Widget _buildActionButton(bool isPortrait, + {required IconData icon, required VoidCallback onPressed, String? tooltip}) { return SizedBox( width: isPortrait ? 35.sp : 30.sp, height: isPortrait ? 35.sp : 30.sp, child: FloatingActionButton( - focusNode: switchToPlayQuranFocusNode, backgroundColor: Colors.black.withOpacity(.3), child: Icon( - autoScrollState.isAutoScrolling ? Icons.play_arrow : Icons.pause, + icon, color: Colors.white, size: isPortrait ? 20.sp : 15.sp, ), - onPressed: () { - ref.read(autoScrollNotifierProvider.notifier).startAutoScroll(); - }, + onPressed: onPressed, heroTag: null, + tooltip: tooltip, ), ); } diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart index 4f5fe13c0..afb880875 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart @@ -75,6 +75,36 @@ class AutoScrollNotifier extends AutoDisposeNotifier { if (newFontSize > state.maxFontSize) newFontSize = 1.0; state = state.copyWith(fontSize: newFontSize); } + + void increaseSpeed() { + double newSpeed = state.autoScrollSpeed + 0.1; + if (newSpeed > 5.0) newSpeed = 5.0; + state = state.copyWith(autoScrollSpeed: newSpeed); + if (state.isAutoScrolling) { + startAutoScroll(); + } + } + + void decreaseSpeed() { + double newSpeed = state.autoScrollSpeed - 0.1; + if (newSpeed < 0.1) newSpeed = 0.1; + state = state.copyWith(autoScrollSpeed: newSpeed); + if (state.isAutoScrolling) { + startAutoScroll(); + } + } + + void increaseFontSize() { + double newFontSize = state.fontSize + 0.2; + if (newFontSize > state.maxFontSize) newFontSize = state.maxFontSize; + state = state.copyWith(fontSize: newFontSize); + } + + void decreaseFontSize() { + double newFontSize = state.fontSize - 0.2; + if (newFontSize < 1.0) newFontSize = 1.0; + state = state.copyWith(fontSize: newFontSize); + } } final autoScrollNotifierProvider = AutoDisposeNotifierProvider( diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart index a3c3d855c..522c18a63 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart @@ -14,7 +14,7 @@ class AutoScrollState { }); // Derived properties - bool get isAutoScrolling => !isSinglePageView; + bool get isAutoScrolling => isSinglePageView; bool get showSpeedControl => !isSinglePageView; AutoScrollState copyWith({ From 6e25aaaf6027a86bb1ed71518802498925745656 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:26:39 +0300 Subject: [PATCH 07/33] fix: scrolling functionality and refactor Quran reading code - Implement auto-scrolling that aligns with the current page and page height. - Refactor floating action buttons into separate widget classes for better code organization. - Update auto-scroll state and notifier to handle scroll controller and dynamic speed adjustments. --- .../quran/reading/quran_reading_screen.dart | 48 ++- .../widget/quran_floating_action_buttons.dart | 364 +++++++++++++----- .../auto_reading/auto_reading_notifier.dart | 69 +++- .../auto_reading/auto_reading_state.dart | 9 +- 4 files changed, 375 insertions(+), 115 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 41c3fef73..129799fa3 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -15,6 +15,8 @@ import 'package:mawaqit/src/services/user_preferences_manager.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/auto_reading/auto_reading_notifier.dart'; +import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; import 'package:mawaqit/src/pages/quran/widget/download_quran_popup.dart'; @@ -138,6 +140,8 @@ class _QuranReadingScreenState extends ConsumerState { _portraitModeSwitchQuranFocusNode.onKeyEvent = (node, event) => _handlePageScrollUpFocusGroupNode(node, event); _portraitModePageSelectorFocusNode.onKeyEvent = (node, event) => _handlePageScrollDownFocusGroupNode(node, event); + final autoReadingState = ref.watch(autoScrollNotifierProvider); + return OrientationBuilder( builder: (context, orientation) { final isPortrait = orientation == Orientation.portrait; @@ -155,7 +159,7 @@ class _QuranReadingScreenState extends ConsumerState { switchQuranModeNode: _switchQuranModeNode, switchToPlayQuranFocusNode: _switchToPlayQuranFocusNode, ), - body: _buildBody(quranReadingState, isPortrait, userPrefs), + body: _buildBody(quranReadingState, isPortrait, userPrefs, autoReadingState), ), ); }, @@ -163,7 +167,11 @@ class _QuranReadingScreenState extends ConsumerState { } Widget _buildBody( - AsyncValue quranReadingState, bool isPortrait, UserPreferencesManager userPrefs) { + AsyncValue quranReadingState, + bool isPortrait, + UserPreferencesManager userPrefs, + AutoScrollState autoScrollState, + ) { final color = Theme.of(context).primaryColor; return quranReadingState.when( loading: () => Center( @@ -178,9 +186,11 @@ class _QuranReadingScreenState extends ConsumerState { data: (quranReadingState) { return Stack( children: [ - isPortrait - ? buildVerticalPageView(quranReadingState, ref) - : buildHorizontalPageView(quranReadingState, ref, context), + autoScrollState.isSinglePageView + ? buildAutoScrollView(quranReadingState, ref, autoScrollState) + : isPortrait + ? buildVerticalPageView(quranReadingState, ref) + : buildHorizontalPageView(quranReadingState, ref, context), if (!isPortrait) ...[ buildRightSwitchButton( context, _rightSkipButtonFocusNode, () => _scrollPageList(ScrollDirection.forward)), @@ -236,6 +246,33 @@ class _QuranReadingScreenState extends ConsumerState { ); } + Widget buildAutoScrollView( + QuranReadingState quranReadingState, + WidgetRef ref, + AutoScrollState autoScrollState, + ) { + return ListView.builder( + controller: autoScrollState.scrollController, + itemCount: quranReadingState.totalPages, + itemBuilder: (context, index) { + final currentPage = quranReadingState.currentPage; + final pageHeight = MediaQuery.of(context).size.height; + + return LayoutBuilder( + builder: (context, constraints) { + final pageHeight = + constraints.maxHeight.isInfinite ? MediaQuery.of(context).size.height : constraints.maxHeight; + return Container( + width: constraints.maxWidth, + height: pageHeight, + child: quranReadingState.svgs[index], + ); + }, + ); + }, + ); + } + Widget buildFloatingPortrait(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.start, @@ -427,4 +464,3 @@ class _QuranReadingScreenState extends ConsumerState { bool _isThereCurrentDialogShowing(BuildContext context) => ModalRoute.of(context)?.isCurrent != true; } - diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 7b49a6bb3..8904c788e 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -5,7 +5,8 @@ import 'package:mawaqit/src/services/user_preferences_manager.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart'; -import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_state.dart'; +import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; +import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; import 'package:provider/provider.dart'; import 'package:sizer/sizer.dart'; @@ -24,66 +25,142 @@ class QuranFloatingActionControls extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final userPrefs = context.watch(); - + final quranReadingState = ref.watch(quranReadingNotifierProvider); return OrientationBuilder( builder: (context, orientation) { final isPortrait = orientation == Orientation.portrait; final autoScrollState = ref.watch(autoScrollNotifierProvider); if (autoScrollState.isAutoScrolling) { - return _buildAutoScrollingReadingMode(isPortrait, ref); + return quranReadingState.maybeWhen( + orElse: () { + return CircularProgressIndicator(); + }, + data: (quranState) { + return _AutoScrollingReadingMode( + isPortrait: isPortrait, + quranReadingState: quranState, + switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, + ); + }, + ); } else { return isPortrait - ? _buildFloatingPortrait(userPrefs, isPortrait, ref, context, autoScrollState) - : _buildFloatingLandscape(userPrefs, isPortrait, ref, context, autoScrollState); + ? _FloatingPortrait( + userPrefs: userPrefs, + isPortrait: isPortrait, + switchScreenViewFocusNode: switchScreenViewFocusNode, + switchQuranModeNode: switchQuranModeNode, + switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, + ) + : _FloatingLandscape( + userPrefs: userPrefs, + isPortrait: isPortrait, + switchScreenViewFocusNode: switchScreenViewFocusNode, + switchQuranModeNode: switchQuranModeNode, + switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, + ); } }, ); } +} + +class _FloatingPortrait extends StatelessWidget { + final UserPreferencesManager userPrefs; + final bool isPortrait; + final FocusNode switchScreenViewFocusNode; + final FocusNode switchQuranModeNode; + final FocusNode switchToPlayQuranFocusNode; - Widget _buildFloatingPortrait( - UserPreferencesManager userPrefs, - bool isPortrait, - WidgetRef ref, - BuildContext context, - AutoScrollState autoScrollState, - ) { + const _FloatingPortrait({ + required this.userPrefs, + required this.isPortrait, + required this.switchScreenViewFocusNode, + required this.switchQuranModeNode, + required this.switchToPlayQuranFocusNode, + }); + + @override + Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - _buildOrientationToggleButton(userPrefs, isPortrait, context), + _OrientationToggleButton( + userPrefs: userPrefs, + isPortrait: isPortrait, + switchScreenViewFocusNode: switchScreenViewFocusNode, + ), SizedBox(width: 200.sp), - _buildQuranModeButton(userPrefs, isPortrait, ref, context), + _QuranModeButton( + userPrefs: userPrefs, + isPortrait: isPortrait, + switchQuranModeNode: switchQuranModeNode, + ), SizedBox(width: 200.sp), - _buildPlayPauseButton( - isPortrait, - ref, - autoScrollState + _PlayPauseButton( + isPortrait: isPortrait, + switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, ), ], ); } +} + +class _FloatingLandscape extends StatelessWidget { + final UserPreferencesManager userPrefs; + final bool isPortrait; + final FocusNode switchScreenViewFocusNode; + final FocusNode switchQuranModeNode; + final FocusNode switchToPlayQuranFocusNode; + + const _FloatingLandscape({ + required this.userPrefs, + required this.isPortrait, + required this.switchScreenViewFocusNode, + required this.switchQuranModeNode, + required this.switchToPlayQuranFocusNode, + }); - Widget _buildFloatingLandscape( - UserPreferencesManager userPrefs, - bool isPortrait, - WidgetRef ref, - BuildContext context, - AutoScrollState autoScrollState, - ) { + @override + Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - _buildPlayPauseButton(isPortrait, ref, autoScrollState), + _PlayPauseButton( + isPortrait: isPortrait, + switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, + ), SizedBox(height: 1.h), - _buildOrientationToggleButton(userPrefs, isPortrait, context), + _OrientationToggleButton( + userPrefs: userPrefs, + isPortrait: isPortrait, + switchScreenViewFocusNode: switchScreenViewFocusNode, + ), SizedBox(height: 1.h), - _buildQuranModeButton(userPrefs, isPortrait, ref, context), + _QuranModeButton( + userPrefs: userPrefs, + isPortrait: isPortrait, + switchQuranModeNode: switchQuranModeNode, + ), ], ); } +} + +class _OrientationToggleButton extends StatelessWidget { + final UserPreferencesManager userPrefs; + final bool isPortrait; + final FocusNode switchScreenViewFocusNode; + + const _OrientationToggleButton({ + required this.userPrefs, + required this.isPortrait, + required this.switchScreenViewFocusNode, + }); - Widget _buildOrientationToggleButton(UserPreferencesManager userPrefs, bool isPortrait, BuildContext context) { + @override + Widget build(BuildContext context) { return SizedBox( width: isPortrait ? 35.sp : 30.sp, height: isPortrait ? 35.sp : 30.sp, @@ -95,20 +172,33 @@ class QuranFloatingActionControls extends ConsumerWidget { color: Colors.white, size: isPortrait ? 20.sp : 15.sp, ), - onPressed: () => _toggleOrientation(userPrefs, context), + onPressed: () => _toggleOrientation(context), heroTag: null, ), ); } - void _toggleOrientation(UserPreferencesManager userPrefs, BuildContext context) { + void _toggleOrientation(BuildContext context) { final newOrientation = - MediaQuery.of(context).orientation == Orientation.portrait ? Orientation.landscape : Orientation.portrait; + MediaQuery.of(context).orientation == Orientation.portrait ? Orientation.landscape : Orientation.portrait; userPrefs.orientationLandscape = newOrientation == Orientation.landscape; } +} + +class _QuranModeButton extends ConsumerWidget { + final UserPreferencesManager userPrefs; + final bool isPortrait; + final FocusNode switchQuranModeNode; - Widget _buildQuranModeButton(UserPreferencesManager userPrefs, bool isPortrait, WidgetRef ref, BuildContext context) { + const _QuranModeButton({ + required this.userPrefs, + required this.isPortrait, + required this.switchQuranModeNode, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { return SizedBox( width: isPortrait ? 35.sp : 30.sp, height: isPortrait ? 35.sp : 30.sp, @@ -136,34 +226,127 @@ class QuranFloatingActionControls extends ConsumerWidget { ), ); } +} + +class _PlayPauseButton extends ConsumerWidget { + final bool isPortrait; + final FocusNode? switchToPlayQuranFocusNode; + + const _PlayPauseButton({ + required this.isPortrait, + this.switchToPlayQuranFocusNode, + }); - Widget _buildAutoScrollingReadingMode(bool isPortrait, WidgetRef ref) { + @override + Widget build(BuildContext context, WidgetRef ref) { final autoScrollState = ref.watch(autoScrollNotifierProvider); + return _ActionButton( + isPortrait: isPortrait, + icon: autoScrollState.isAutoScrolling ? Icons.pause : Icons.play_arrow, + onPressed: () { + final quranReadingStateAsync = ref.read(quranReadingNotifierProvider); + final quranReadingState = quranReadingStateAsync.asData?.value; + + if (quranReadingState != null) { + final currentPage = quranReadingState.currentPage; + final pageHeight = MediaQuery.of(context).size.height; + + ref.read(autoScrollNotifierProvider.notifier).toggleAutoScroll(currentPage, pageHeight); + } + }, + tooltip: autoScrollState.isAutoScrolling ? 'Pause' : 'Play', + focusNode: switchToPlayQuranFocusNode, + ); + } +} + +class _ActionButton extends StatelessWidget { + final bool isPortrait; + final IconData icon; + final VoidCallback onPressed; + final String? tooltip; + final FocusNode? focusNode; + + const _ActionButton({ + required this.isPortrait, + required this.icon, + required this.onPressed, + this.tooltip, + this.focusNode, + }); + @override + Widget build(BuildContext context) { + return SizedBox( + width: isPortrait ? 35.sp : 30.sp, + height: isPortrait ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: focusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + icon, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, + ), + onPressed: onPressed, + heroTag: null, + tooltip: tooltip, + ), + ); + } +} + +class _AutoScrollingReadingMode extends ConsumerWidget { + final bool isPortrait; + final QuranReadingState quranReadingState; + final FocusNode? switchToPlayQuranFocusNode; + + const _AutoScrollingReadingMode({ + required this.isPortrait, + required this.quranReadingState, + this.switchToPlayQuranFocusNode, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final autoScrollState = ref.watch(autoScrollNotifierProvider); return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - _buildFontSizeControls(isPortrait, ref), + _FontSizeControls(isPortrait: isPortrait), SizedBox(height: 1.h), - _buildSpeedControls(isPortrait, ref), + _SpeedControls( + quranReadingState: quranReadingState, + isPortrait: isPortrait, + ), SizedBox(height: 1.h), - _buildPlayPauseButton(isPortrait, ref, autoScrollState), + _PlayPauseButton( + isPortrait: isPortrait, + switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, + ), ], ); } +} - Widget _buildFontSizeControls(bool isPortrait, WidgetRef ref) { +class _FontSizeControls extends ConsumerWidget { + final bool isPortrait; + + const _FontSizeControls({required this.isPortrait}); + + @override + Widget build(BuildContext context, WidgetRef ref) { return Column( children: [ - _buildActionButton( - isPortrait, + _ActionButton( + isPortrait: isPortrait, icon: Icons.remove, onPressed: () => ref.read(autoScrollNotifierProvider.notifier).decreaseFontSize(), tooltip: 'Decrease Font Size', ), SizedBox(width: 1.h), - _buildActionButton( - isPortrait, + _ActionButton( + isPortrait: isPortrait, icon: Icons.add, onPressed: () => ref.read(autoScrollNotifierProvider.notifier).increaseFontSize(), tooltip: 'Increase Font Size', @@ -171,54 +354,61 @@ class QuranFloatingActionControls extends ConsumerWidget { ], ); } +} - Widget _buildSpeedControls(bool isPortrait, WidgetRef ref) { - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildActionButton( - isPortrait, - icon: Icons.fast_rewind, - onPressed: () => ref.read(autoScrollNotifierProvider.notifier).decreaseSpeed(), - tooltip: 'Decrease Speed', - ), - SizedBox(width: 2.h), - _buildActionButton( - isPortrait, - icon: Icons.fast_forward, - onPressed: () => ref.read(autoScrollNotifierProvider.notifier).increaseSpeed(), - tooltip: 'Increase Speed', - ), - ], - ); - } +class _SpeedControls extends ConsumerWidget { + final QuranReadingState quranReadingState; + final bool isPortrait; - Widget _buildPlayPauseButton(bool isPortrait, WidgetRef ref, AutoScrollState autoScrollState) { - return _buildActionButton( - isPortrait, - icon: autoScrollState.isAutoScrolling ? Icons.pause : Icons.play_arrow, - onPressed: () { - ref.read(autoScrollNotifierProvider.notifier).toggleAutoScroll(); - }, - tooltip: autoScrollState.isAutoScrolling ? 'Pause' : 'Play', - ); - } + const _SpeedControls({ + required this.quranReadingState, + required this.isPortrait, + }); - Widget _buildActionButton(bool isPortrait, - {required IconData icon, required VoidCallback onPressed, String? tooltip}) { - return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, - child: FloatingActionButton( - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - icon, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: onPressed, - heroTag: null, - tooltip: tooltip, + @override + Widget build(BuildContext context, WidgetRef ref) { + return Positioned( + right: 16, + bottom: isPortrait ? 100 : 16, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + mini: true, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + Icons.add, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, + ), + onPressed: () { + final pageHeight = MediaQuery.of(context).size.height; + ref.read(autoScrollNotifierProvider.notifier).increaseSpeed( + quranReadingState.currentPage, + pageHeight, + ); + }, + heroTag: 'increase_speed', + ), + SizedBox(height: 8), + FloatingActionButton( + mini: true, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + Icons.remove, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, + ), + onPressed: () { + final pageHeight = MediaQuery.of(context).size.height; + ref.read(autoScrollNotifierProvider.notifier).decreaseSpeed( + quranReadingState.currentPage, + pageHeight, + ); + }, + heroTag: 'decrease_speed', + ), + ], ), ); } diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart index afb880875..f3a1be2ad 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart @@ -17,31 +17,60 @@ class AutoScrollNotifier extends AutoDisposeNotifier { _hideTimer?.cancel(); scrollController.dispose(); }); - return AutoScrollState(); + return AutoScrollState( + scrollController: scrollController, + ); + } + + Future jumpToCurrentPage(int currentPage, double pageHeight) async { + if (scrollController.hasClients) { + final offset = (currentPage - 1) * pageHeight; + scrollController.jumpTo(offset); + } } - void toggleAutoScroll() { - state = state.copyWith( - isSinglePageView: !state.isSinglePageView, - ); + void toggleAutoScroll(int currentPage, double pageHeight) { if (state.isAutoScrolling) { - startAutoScroll(); - } else { stopAutoScroll(); + } else { + startAutoScroll(currentPage, pageHeight); } } - void startAutoScroll() { + Future startAutoScroll(int currentPage, double pageHeight) async { _autoScrollTimer?.cancel(); state = state.copyWith( isSinglePageView: true, ); - _autoScrollTimer = Timer.periodic(Duration(milliseconds: 50), (timer) { - if (scrollController.position.pixels < scrollController.position.maxScrollExtent) { - scrollController.jumpTo(scrollController.position.pixels + (state.autoScrollSpeed * 2)); - } else { - stopAutoScroll(); + + // Ensure the ListView is built + await Future.delayed(Duration(milliseconds: 50)); + + // Jump to the current page + if (scrollController.hasClients) { + final offset = (currentPage - 1) * pageHeight; + scrollController.jumpTo(offset); + } + + _startScrolling(); + } + + + + void _startScrolling() { + const duration = Duration(milliseconds: 50); + _autoScrollTimer = Timer.periodic(duration, (timer) { + if (scrollController.hasClients) { + final maxScroll = scrollController.position.maxScrollExtent; + final currentScroll = scrollController.offset; + final delta = state.autoScrollSpeed; + + if (currentScroll >= maxScroll) { + stopAutoScroll(); + } else { + scrollController.jumpTo(currentScroll + delta); + } } }); } @@ -49,13 +78,13 @@ class AutoScrollNotifier extends AutoDisposeNotifier { void stopAutoScroll() { _autoScrollTimer?.cancel(); _autoScrollTimer = null; + state = state.copyWith( + isSinglePageView: false, + ); } void changeSpeed(double newSpeed) { state = state.copyWith(autoScrollSpeed: newSpeed.clamp(0.1, 5.0)); - if (state.isAutoScrolling) { - startAutoScroll(); - } } void showControls() { @@ -76,21 +105,21 @@ class AutoScrollNotifier extends AutoDisposeNotifier { state = state.copyWith(fontSize: newFontSize); } - void increaseSpeed() { + void increaseSpeed(int currentPage, double pageHeight) { double newSpeed = state.autoScrollSpeed + 0.1; if (newSpeed > 5.0) newSpeed = 5.0; state = state.copyWith(autoScrollSpeed: newSpeed); if (state.isAutoScrolling) { - startAutoScroll(); + startAutoScroll(currentPage, pageHeight); } } - void decreaseSpeed() { + void decreaseSpeed(int currentPage, double pageHeight) { double newSpeed = state.autoScrollSpeed - 0.1; if (newSpeed < 0.1) newSpeed = 0.1; state = state.copyWith(autoScrollSpeed: newSpeed); if (state.isAutoScrolling) { - startAutoScroll(); + startAutoScroll(currentPage, pageHeight); } } diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart index 522c18a63..a0e7b0aaf 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart @@ -1,11 +1,15 @@ +import 'package:flutter/material.dart'; + class AutoScrollState { final bool isSinglePageView; final double autoScrollSpeed; final bool isVisible; final double fontSize; final double maxFontSize; + final ScrollController scrollController; AutoScrollState({ + required this.scrollController, this.isSinglePageView = false, this.autoScrollSpeed = 1.0, this.isVisible = true, @@ -13,8 +17,8 @@ class AutoScrollState { this.maxFontSize = 3.0, }); - // Derived properties bool get isAutoScrolling => isSinglePageView; + bool get showSpeedControl => !isSinglePageView; AutoScrollState copyWith({ @@ -23,6 +27,7 @@ class AutoScrollState { bool? isVisible, double? fontSize, double? maxFontSize, + ScrollController? scrollController, }) { return AutoScrollState( isSinglePageView: isSinglePageView ?? this.isSinglePageView, @@ -30,6 +35,7 @@ class AutoScrollState { isVisible: isVisible ?? this.isVisible, fontSize: fontSize ?? this.fontSize, maxFontSize: maxFontSize ?? this.maxFontSize, + scrollController: scrollController ?? this.scrollController, ); } @@ -45,5 +51,4 @@ class AutoScrollState { 'showSpeedControl: $showSpeedControl' ')'; } - } From 87834500f36b2e15f7b6ce7d0cbad735a78bc6e6 Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 24 Oct 2024 14:23:05 +0300 Subject: [PATCH 08/33] refactor QuranReadingScreen: Remove unused imports and redundant widget functions - Removed unnecessary imports such as SvgPicture and ReciterSelectionScreen. - Cleaned up redundant widget methods like `buildFloatingPortrait`, `buildFloatingLandscape`, and other floating action button handlers. - Simplified the UI logic by eliminating unused `QuranModeButton` and `PlayToggleButton` widgets. --- .../quran/reading/quran_reading_screen.dart | 115 ------------------ 1 file changed, 115 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 129799fa3..9796c39c2 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -4,9 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:mawaqit/i18n/l10n.dart'; -import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; import 'package:mawaqit/src/pages/quran/reading/widget/quran_floating_action_buttons.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_reading_widgets.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_surah_selector.dart'; @@ -14,7 +12,6 @@ import 'package:mawaqit/src/pages/quran/widget/reading/quran_surah_selector.dart import 'package:mawaqit/src/services/user_preferences_manager.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/auto_reading/auto_reading_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; @@ -25,8 +22,6 @@ import 'package:provider/provider.dart' as provider; import 'package:sizer/sizer.dart'; -import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; - import 'package:mawaqit/src/pages/quran/widget/reading/quran_reading_page_selector.dart'; class QuranReadingScreen extends ConsumerStatefulWidget { @@ -255,8 +250,6 @@ class _QuranReadingScreenState extends ConsumerState { controller: autoScrollState.scrollController, itemCount: quranReadingState.totalPages, itemBuilder: (context, index) { - final currentPage = quranReadingState.currentPage; - final pageHeight = MediaQuery.of(context).size.height; return LayoutBuilder( builder: (context, constraints) { @@ -273,99 +266,6 @@ class _QuranReadingScreenState extends ConsumerState { ); } - Widget buildFloatingPortrait(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - _buildOrientationToggleButton(isPortrait, userPrefs), - SizedBox(width: 200.sp), - _buildQuranModeButton(isPortrait, userPrefs, context), - SizedBox(width: 200.sp), - _buildPlayToggleButton(isPortrait), - ], - ); - } - - Widget buildFloatingLandscape(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _buildPlayToggleButton(isPortrait), - SizedBox(height: 1.h), - _buildOrientationToggleButton(isPortrait, userPrefs), - SizedBox(height: 1.h), - _buildQuranModeButton(isPortrait, userPrefs, context), - ], - ); - } - - Widget _buildOrientationToggleButton(bool isPortrait, UserPreferencesManager userPrefs) { - return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: _switchScreenViewFocusNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - !isPortrait ? Icons.stay_current_portrait : Icons.stay_current_landscape, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: () => _toggleOrientation(userPrefs), - heroTag: null, - ), - ); - } - - Widget _buildQuranModeButton(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { - return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: _switchQuranModeNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - Icons.headset, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: () async { - ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); - if (isPortrait) { - userPrefs.orientationLandscape = true; - } - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => ReciterSelectionScreen.withoutSurahName(), - ), - ); - }, - heroTag: null, - ), - ); - } - - Widget _buildPlayToggleButton(bool isPortrait) { - return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: _switchToPlayQuranFocusNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - !isPortrait ? Icons.play_arrow : Icons.stay_current_landscape, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: () { - ; - }, - heroTag: null, - ), - ); - } - void _scrollPageList(ScrollDirection direction) { final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; if (direction == ScrollDirection.forward) { @@ -374,21 +274,6 @@ class _QuranReadingScreenState extends ConsumerState { ref.read(quranReadingNotifierProvider.notifier).nextPage(isPortrait: isPortrait); } } - - Widget _buildSvgPicture(SvgPicture svgPicture) { - return Container( - color: Colors.white, - padding: EdgeInsets.all(32.0), - child: SvgPicture( - svgPicture.bytesLoader, - fit: BoxFit.contain, - width: double.infinity, - height: double.infinity, - alignment: Alignment.center, - ), - ); - } - void _showPageSelector(BuildContext context, int totalPages, int currentPage) { showDialog( context: context, From 431d33bf1007f754d41a828f4ea7836c90b3a3bc Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 24 Oct 2024 14:38:04 +0300 Subject: [PATCH 09/33] merge on main --- .github/workflows/code_quality_check.yml | 5 +- .../main/kotlin/com/flyweb/MainActivity.kt | 1 + lib/l10n/intl_ar.arb | 18 +- lib/l10n/intl_bg.arb | 221 ++++- lib/l10n/intl_fr.arb | 38 +- lib/l10n/intl_pl.arb | 207 ++++- lib/src/const/constants.dart | 8 +- .../data_source/device_info_data_source.dart | 30 +- .../quran/quran_local_data_source.dart | 26 +- lib/src/data/repository/device_info_impl.dart | 12 + lib/src/data/repository/quran/quran_impl.dart | 37 +- lib/src/domain/error/quran_exceptions.dart | 5 + .../domain/repository/device_repository.dart | 3 + .../pages/quran/page/quran_player_screen.dart | 851 +++++++++--------- .../quran/page/reciter_selection_screen.dart | 9 +- .../quran/reading/quran_reading_screen.dart | 200 ++-- .../quran/widget/reading/moshaf_selector.dart | 4 +- .../reading/quran_reading_page_selector.dart | 94 +- .../widget/reading/quran_reading_widgets.dart | 64 +- lib/src/services/mosque_manager.dart | 8 +- .../quran/recite/recite_notifier.dart | 27 +- .../quran/recite/recite_state.dart | 5 +- pubspec.yaml | 2 +- 23 files changed, 1275 insertions(+), 600 deletions(-) diff --git a/.github/workflows/code_quality_check.yml b/.github/workflows/code_quality_check.yml index 67e08d395..c3668975f 100644 --- a/.github/workflows/code_quality_check.yml +++ b/.github/workflows/code_quality_check.yml @@ -2,8 +2,9 @@ name: Check source code quality on: pull_request: - branches: ["main"] - + branches: + - main + - 'release-*' jobs: lint: name: Check lints diff --git a/android/app/src/main/kotlin/com/flyweb/MainActivity.kt b/android/app/src/main/kotlin/com/flyweb/MainActivity.kt index a4fa66abb..4f137c00f 100644 --- a/android/app/src/main/kotlin/com/flyweb/MainActivity.kt +++ b/android/app/src/main/kotlin/com/flyweb/MainActivity.kt @@ -59,6 +59,7 @@ class MainActivity : FlutterActivity() { val isSuccess = clearDataRestart() result.success(isSuccess) } + else -> result.notImplemented() } } diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index ffe1365dc..a6c6af234 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -292,7 +292,7 @@ }, "chooseQuranPage": "اختر الصفحة", "checkingForUpdates": "التحقق من وجود تحديث...", - "chooseQuranType": "اختر القرآن الكريم", + "chooseQuranType": "إختر الرواية", "hafs": "حفص", "warsh": "ورش", "favorites": "المفضلة", @@ -338,5 +338,19 @@ } } }, - "surahSelector": "اختر السورة" + "surahSelector": "اختر السورة", + "quranReadingPagePortrait": "الصفحة {currentPage} / {totalPages}", + "@quranReadingPagePortrait": { + "description": "Placeholder text for displaying Quran reading page portrait numbers", + "placeholders": { + "currentPage": { + "type": "int", + "example": "1" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } + } } diff --git a/lib/l10n/intl_bg.arb b/lib/l10n/intl_bg.arb index 4813763f3..cf0aa55c6 100644 --- a/lib/l10n/intl_bg.arb +++ b/lib/l10n/intl_bg.arb @@ -1,18 +1,19 @@ { "home": "Начало", "share": "Споделете", - "about": "За", + "about": "За приложението", "rate": "Оценете ни", "languages": "Езици", "appLang": "Език на приложението", "descLang": "Моля, изберете предпочитания от вас език", + "hadithLangDesc": "Можете да изберете езика на MawaqitTV на това устройство, независимо от вашите настройки за езикови предпочитания в администраторската конзола на mawaqit.net", "whoops": "Упс!", "noInternet": "Няма интернет връзка", "tryAgain": "Опитайте отново", "closeApp": "Затваряне на приложението", "quit": "Напуснете", "forceStaging": "Преминаване към постановка", - "disableStaging": "Преминаване към производство", + "disableStaging": "Преминете към производствен режим", "sureCloseApp": "Сигурни ли сте, че искате да прекратите приложението?", "ok": "ОК", "cancel": "ОТМЕНЯНЕ", @@ -45,12 +46,12 @@ "mosqueNoMore": "Няма повече резултати", "mosqueNoResults": "Няма резултати", "offline": "Офлайн", - "imsak": "Imsak", - "jumua": "Jumua", - "duhr": "Duhr", - "fajr": "Fajr", - "asr": "Asr", - "maghrib": "Maghrib", + "imsak": "Имсак", + "jumua": "Петък (Джума)", + "duhr": "Обедна", + "fajr": "Сутрешна", + "asr": "Следобедна ", + "maghrib": "Вечерна", "isha": "Иша", "afterAdhanHadithTitle": "След adhan Du`aa", "afterSalahHadith": "Аллахумма Рабба хадхихид-да'вати-таммати, ес-салатил-ка'имати, ати Мухаммаданил-василата вал-фадилата, уаб'атху макаман махмуда нилади уа 'адтаху [О, Аллах, Рубе на този съвършен призив (Да'уа) и на установената молитва (Ас-салат), дай на Мухаммад василата и превъзходството и го издигни до похвално положение, което си му обещал]", @@ -87,6 +88,34 @@ "@azkarList6": { "description": "لا إِلَٰهَ إلاّ اللّهُ وحدَهُ لا شريكَ لهُ، لهُ المُـلْكُ ولهُ الحَمْد، وهوَ على كلّ شَيءٍ قَدير، اللّهُـمَّ لا مانِعَ لِما أَعْطَـيْت، وَلا مُعْطِـيَ لِما مَنَـعْت، وَلا يَنْفَـعُ ذا الجَـدِّ مِنْـكَ الجَـد" }, + "azkarList7": "اللَّهمَّ أنتَ ربِّي وأنا عبدُكَ لا إلهَ إلَّا أنتَ خلَقْتَني وأنا عبدُكَ أصبَحْتُ على عهدِكَ ووَعْدِكَ ما استطَعْتُ أعوذُ بكَ مِن شرِّ ما صنَعْتُ وأبوءُ لكَ بنعمتِكَ علَيَّ وأبوءُ لكَ بذُنوبي فاغفِرْ لي إنَّه لا يغفِرُ الذُّنوبَ إلَّا أنتَ", + "@azkarList7": { + "description": "اللهم أنت ربي، لا إله إلا أنت، خلقتني وأنا عبدُك, وأنا على عهدِك ووعدِك ما استطعتُ، أعوذ بك من شر ما صنعتُ، أبوءُ لَكَ بنعمتكَ عَلَيَّ، وأبوء بذنبي، فاغفر لي، فإنه لا يغفرُ الذنوب إلا أنت" + }, + "azkarList8": "أَصْبَحْنَا وَأَصْبَحَ الْمُلْكُ لِلهِ وَالْحَمْدُ لِلهِ لَا إِلَهَ إِلَّا الله وَحْدَهُ لَا شَرِيكَ لَهُ ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ . رَبِّ إنِّي أَسْأَلُكَ خَيْرَ مَا فِي هَذَا اليَوم وَخَيْرَ مَا بَعْدَهُ وَأَعُوذُ بِكَ مِنْ شَرِّ هَذَا اليَوم وَشَرِّ مَا بَعْدَهُ . رَبِّ أَعُوذُ بِكَ مِنَ الْكَسَلِ وَسُوءِ الْكِبَرِ . رَبِّ أَعُوذُ بِكَ مِنْ عَذَابٍ فِي النَّارِ وَعَذَابٍ فِي الْقَبْرِ", + "@azkarList8": { + "description": "أصبحنا وأصبح الملك لله، والحمد لله ولا إله إلا الله وحده لا شريك له، له الملك وله الحمد، وهو على كل شيء قدير، أسألك خير ما في هذا اليوم، وخير ما بعده، وأعوذ بك من شر هذا اليوم، وشر ما بعده، وأعوذ بك من الكسل وسوء الكبر، وأعوذ بك من عذاب النار وعذاب القبر" + }, + "azkarList9": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ |أربعَ مَرَّات|. [ وإذا أمسى قال: اللَّهم إني أمسيت...]", + "@azkarList9": { + "description": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ |أربعَ مَرَّات|. [ وإذا أمسى قال: اللَّهم إني أمسيت...]" + }, + "azkarList10": " 3 пъти | اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ |", + "@azkarList10": { + "description": "|اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ |ثلاثَ مرَّاتٍ" + }, + "azkarList11": " 7 пъти | حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ", + "@azkarList11": { + "description": "|حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ |سَبْعَ مَرّاتٍ" + }, + "azkarList12": " 3 пъти | رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً ", + "@azkarList12": { + "description": "|رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً |ثلاثَ مرَّاتٍ" + }, + "azkarList13": " 10 пъти | لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ", + "@azkarList13": { + "description": "|لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ |عشرَ مرَّات" + }, "jumuaaScreenTitle": "Време Jumuaa", "jumuaaHadith": "Пророкът ﷺ (мир и благословение на Аллах върху него) каза: \"Който се измие перфектно, след това отива на джумуа и след това слуша и мълчи, му се прощава това, което е между този момент и следващия петък и още три дни, а този, който докосва камъни, със сигурност е направил безсмислица\".", "shuruk": "Shuruk", @@ -147,9 +176,181 @@ "mainScreenExplanation": "За главната зала на джамията този екран няма да показва джумуа на живо.", "normalModeExplanation": "Ще се покаже нормалният екран с времето за молитва и съобщенията.", "announcementOnlyModeExplanation": "Ще показва съобщения през цялото време", + "orientation": "Ориентация", + "selectYourMawaqitTvAppOrientation": "Изберете ориентацията на вашето приложение mawaqit tv", + "deviceDefault": "Устройство по подразбиране", + "deviceDefaultBTNDescription": "Mawaqit автоматично ще избере ориентацията по подразбиране въз основа на ориентацията на екрана", + "portrait": "Портрет", + "portraitBTNDescription": "За вертикална ориентация се препоръчва за джамии с малко пространство", + "landscape": "Пейзаж", + "landscapeBTNDescription": "За хоризонтална ориентация. Основното оформление за приложението mawaqit tv и препоръчително за повечето джамии", "eidMubarak": "Ид Мубарак", "takbeerAleidText": "Аллаху Акбар, Аллаху Акбар, Аллаху Акбар, Аллаху Акбар, ла илаха ила Аллах, Аллаху Акбар, Аллаху Акбар, Аллаху Акбар, уа лилахи ал-хамд", "settings": "Настройки", - "applicationModes": "Режими на приложение", - "ifYouAreFacingAnIssueWithTheAppActivateThis": "Ако срещнете проблем с приложението, активната опция" + "applicationModes": "Режим на приложение", + "ifYouAreFacingAnIssueWithTheAppActivateThis": "Ако срещате проблеми с приложението, опитайте се да разрешите тази опция.", + "hijriAdjustments": "Местни хиджри корекции", + "hijriAdjustmentsDescription": "Коригирайте датата на хиджра локално във вашето устройство. Това няма да повлияе на настройките на онлайн джамията", + "backoffice_default": "Backoffice по подразбиране", + "recommended": "Препоръчва се", + "sabah": "Сутрешна", + "randomHadithLanguage": "Произволен език на хадисите", + "en": "Английски език", + "fr": "Френски", + "ar": "Арабски", + "tr": "Турски", + "de": "Немски", + "es": "Испански", + "pt": "Произволен език на хадисите", + "nl": "Холандски", + "fr_ar": "Френски и арабски", + "en_ar": "Английски и арабски", + "de_ar": "Немски и арабски", + "ta_ar": "Тамилски и арабски", + "tr_ar": "Турски и арабски", + "es_ar": "Испански и арабски", + "pt_ar": "Португалски и арабски", + "nl_ar": "Холандски и арабски", + "connectToChangeHadith": "Моля, свържете се с интернет, за да промените езика на хадисите.", + "retry": "Повторение", + "timeSetting": "Конфигуриране на времето", + "timeSettingDesc": "Задаване на потребителско име", + "selectedTime": "Текущото избрано време", + "confirmation": "Потвърждение", + "confirmationMessage": "Сигурни ли сте, че искате да използвате времето на устройството?", + "useDeviceTime": "Време за използване на устройството", + "selectTime": "Изберете Време", + "previous": "Предишни", + "appTimezone": "Времева зона на приложението", + "descTimezone": "Няма намерени близки точки за достъп с Wifi", + "appWifi": "Свързване с Wi-Fi", + "descWifi": "Моля, свържете се с предпочитания от вас Wi-Fi", + "searchCountries": "Търсене на държави", + "scanAgain": "Сканирайте отново", + "noScannedResultsFound": "Не са открити близки точки за достъп", + "connect": "Свързване", + "wifiPassword": "Парола", + "skip": "Пропусни", + "noSSID": "**Скрит SSID**", + "close": "Затвори", + "search": "Търсене", + "wifiSuccess": "Успешно се свържете с Wi-Fi.", + "wifiFailure": "Не успя да се свърже с Wifi.", + "timezoneSuccess": "Времевата зона е зададена успешно.", + "timezoneFailure": "Не успя да зададе часова зона.", + "screenLock": "Екран включен/изключен", + "screenLockConfig": "Конфигуриране на включването/изключването на екрана", + "screenLockMode": "Режим на включване/изключване на екрана", + "screenLockDesc": "Включвайте/изключвайте телевизора преди и след всяка молитва, за да пестите енергия", + "screenLockDesc2": "Тази функция включва/изключва устройството преди и след всяка молитва adhan", + "before": "минути преди всяка молитва", + "after": "минути преди всяка молитва", + "updateAvailable": "Има актуализация", + "seeMore": "Вижте още", + "whatIsNew": "Какво ново", + "update": "Актуализация", + "automaticUpdate": "Уведомете за актуализация", + "automaticUpdateDescription": "Активирайте актуализацията на известията, за да получавате най-новите функции и подобрения", + "checkInternetLegacyMode": "Трябва да се свържете с \"интернет\", за да използвате стария режим", + "powerOnScreen": "Изключване на екрана", + "powerOffScreen": "Изключване на екрана", + "deviceSettings": "Настройка на устройство", + "later": "По късно", + "downloadQuran": "Изтеглете Коран", + "quran": "Коран", + "askDownloadQuran": "Искате ли да изтеглите Свещения Коран?", + "download": "Изтегляне", + "downloadingQuran": "Изтегляне на Корана", + "extractingQuran": "Извличане на Корана", + "updatedQuran": "Коранът е актуализиран", + "quranLatestVersion": "Коранът е актуален", + "quranUpdatedVersion": "Актуализираната версия на Корана е: {version}", + "quranIsUpdated": "Коранът е актуализиран", + "quranDownloaded": "Изтегляне на Коран", + "quranIsAlreadyDownloaded": "Коранът вече е изтеглен", + "chooseReciter": "Изберете четец", + "reciteType": "Тип четене", + "readingMode": "Искам да чета", + "listeningMode": "Искам да слушам", + "quranReadingPage": "Страница {leftPage} - {rightPage} / {totalPages}", + "@quranReadingPage": { + "description": "Placeholder text for displaying Quran reading page numbers", + "placeholders": { + "leftPage": { + "type": "int", + "example": "1" + }, + "rightPage": { + "type": "int", + "example": "2" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } + }, + "quranReadingPagePortrait": "Page {currentPage} / {totalPages}", + "@quranReadingPagePortrait": { + "description": "Placeholder text for displaying Quran reading page portrait numbers", + "placeholders": { + "currentPage": { + "type": "int", + "example": "1" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } + }, + "chooseQuranPage": "Изберете страница", + "checkingForUpdates": "Проверка за актуализации...", + "chooseQuranType": "Изберете Коран", + "hafs": "Хафс", + "warsh": "\"Уарш\" ", + "favorites": "Фаворити", + "allReciters": "Всички четци", + "reciterAddedToFavorites": "Reciter {name} премахнат от любими", + "@reciterAddedToFavorites": { + "description": "Message shown when a reciter is added to favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Abdul Basit" + } + } + }, + "reciterRemovedFromFavorites": "Reciter {name} премахнат от любими", + "@reciterRemovedFromFavorites": { + "description": "Message shown when a reciter is removed from favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Abdul Basit" + } + } + }, + "noFavoriteReciters": "Няма любими рецитатори. Опитайте се да добавите някой в списъка", + "@noFavoriteReciters": { + "description": "Message shown when there are no favorite reciters" + }, + "noReciterSearchResult": "No results found for your search", + "searchForReciter": "Search for a reciter", + "downloadAllSuwarSuccessfully": "Свещеният Коран е свален", + "noSuwarDownload": "Няма нови сури за изтегляне", + "connectDownloadQuran": "Моля, свържете се с интернет, за да изтеглите", + "playInOnlineModeQuran": "Моля, свържете се с интернет, за да играете", + "downloaded": "Свалено", + "switchQuranType": "Go to {name}", + "@switchQuranType": { + "description": "Message shown when a reciter is added to favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Warsh" + } + } + }, + "surahSelector": "Select Surah" } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index d74cf99da..5e96d18d2 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -316,6 +316,42 @@ "example": "Abdul Basit" } } + }, + "noFavoriteReciters": "Aucun réciteur préféré. Essayez d'en ajouter un à la liste", + "@noFavoriteReciters": { + "description": "Message shown when there are no favorite reciters" + }, + "noReciterSearchResult": "Aucun résultat trouvé pour votre recherche.", + "searchForReciter": "Chercher un réciteur", + "downloadAllSuwarSuccessfully": "Tout le Coran est téléchargé", + "noSuwarDownload": "Aucune nouvelle sourate à télécharger", + "connectDownloadQuran":"Veuillez vous connecter à Internet pour télécharger", + "playInOnlineModeQuran": "Veuillez vous connecter à Internet pour jouer", + "downloaded": "Téléchargé", + "switchQuranType": "Aller à {name}", + "@switchQuranType": { + "description": "Message shown when a reciter is added to favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Warsh" + } + } + }, + "surahSelector":"Sélectionner une sourat", + "quranReadingPagePortrait": "Page {currentPage} / {totalPages}", + "@quranReadingPagePortrait": { + "description": "Placeholder text for displaying Quran reading page portrait numbers", + "placeholders": { + "currentPage": { + "type": "int", + "example": "1" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } } -} \ No newline at end of file +} diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 44ca33937..b3a66dc3b 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -6,13 +6,14 @@ "languages": "Języki", "appLang": "Język aplikacji", "descLang": "Proszę wybrać preferowany język", + "hadithLangDesc": "To nadpisuje twój wybór w konsoli administratora, możesz wybrać inny język na ekranie", "whoops": "Ups!", "noInternet": "Brak połączenia z Internetem", "tryAgain": "Spróbuj jeszcze raz", "closeApp": "Zamknij aplikację", "quit": "Zrezygnuj z", "forceStaging": "Przełączanie na etap", - "disableStaging": "Przejście do produkcji", + "disableStaging": "Przełącz na tryb produkcji", "sureCloseApp": "Czy na pewno chce Pan zrezygnować z aplikacji?", "ok": "OK", "cancel": "CANCEL", @@ -87,6 +88,34 @@ "@azkarList6": { "description": "لا إِلَٰهَ إلاّ اللّهُ وحدَهُ لا شريكَ لهُ، لهُ المُـلْكُ ولهُ الحَمْد، وهوَ على كلّ شَيءٍ قَدير، اللّهُـمَّ لا مانِعَ لِما أَعْطَـيْت، وَلا مُعْطِـيَ لِما مَنَـعْت، وَلا يَنْفَـعُ ذا الجَـدِّ مِنْـكَ الجَـد" }, + "azkarList7": "اللهم أنت ربي، لا إله إلا أنت، خلقتني وأنا عبدُك, وأنا على عهدِك ووعدِك ما استطعتُ، أعوذ بك من شر ما صنعتُ، أبوءُ لَكَ بنعمتكَ عَلَيَّ، وأبوء بذنبي، فاغفر لي، فإنه لا يغفرُ الذنوب إلا أنت", + "@azkarList7": { + "description": "اللهم أنت ربي، لا إله إلا أنت، خلقتني وأنا عبدُك, وأنا على عهدِك ووعدِك ما استطعتُ، أعوذ بك من شر ما صنعتُ، أبوءُ لَكَ بنعمتكَ عَلَيَّ، وأبوء بذنبي، فاغفر لي، فإنه لا يغفرُ الذنوب إلا أنت" + }, + "azkarList8": "أصبحنا وأصبح الملك لله، والحمد لله ولا إله إلا الله وحده لا شريك له، له الملك وله الحمد، وهو على كل شيء قدير، أسألك خير ما في هذا اليوم، وخير ما بعده، وأعوذ بك من شر هذا اليوم، وشر ما بعده، وأعوذ بك من الكسل وسوء الكبر، وأعوذ بك من عذاب النار وعذاب القبر", + "@azkarList8": { + "description": "أصبحنا وأصبح الملك لله، والحمد لله ولا إله إلا الله وحده لا شريك له، له الملك وله الحمد، وهو على كل شيء قدير، أسألك خير ما في هذا اليوم، وخير ما بعده، وأعوذ بك من شر هذا اليوم، وشر ما بعده، وأعوذ بك من الكسل وسوء الكبر، وأعوذ بك من عذاب النار وعذاب القبر" + }, + "azkarList9": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ |أربعَ مَرَّات|. [ وإذا أمسى قال: اللَّهم إني أمسيت...]", + "@azkarList9": { + "description": "اللَّهُمَّ إِنِّي أَصْبَحْتُ أُشْهِدُكَ، وَأُشْهِدُ حَمَلَةَ عَرْشِكَ، وَمَلاَئِكَتِكَ، وَجَمِيعَ خَلْقِكَ، أَنَّكَ أَنْتَ اللَّهُ لَا إِلَهَ إِلاَّ أَنْتَ وَحْدَكَ لاَ شَرِيكَ لَكَ، وَأَنَّ مُحَمَّداً عَبْدُكَ وَرَسُولُكَ |أربعَ مَرَّات|. [ وإذا أمسى قال: اللَّهم إني أمسيت...]" + }, + "azkarList10": "|اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ |ثلاثَ مرَّاتٍ", + "@azkarList10": { + "description": "|اللَّهُمَّ عَافِنِي فِي بَدَنِي، اللَّهُمَّ عَافِنِي فِي سَمْعِي، اللَّهُمَّ عَافِنِي فِي بَصَرِي، لاَ إِلَهَ إِلاَّ أَنْتَ. اللَّهُمَّ إِنِّي أَعُوذُ بِكَ مِنَ الْكُفْرِ، وَالفَقْرِ، وَأَعُوذُ بِكَ مِنْ عَذَابِ القَبْرِ، لاَ إِلَهَ إِلاَّ أَنْتَ |ثلاثَ مرَّاتٍ" + }, + "azkarList11": "|حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ |سَبْعَ مَرّاتٍ", + "@azkarList11": { + "description": "|حَسْبِيَ اللَّهُ لاَ إِلَهَ إِلاَّ هُوَ عَلَيهِ تَوَكَّلتُ وَهُوَ رَبُّ الْعَرْشِ الْعَظِيمِ |سَبْعَ مَرّاتٍ" + }, + "azkarList12": "|رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً |ثلاثَ مرَّاتٍ", + "@azkarList12": { + "description": "|رَضِيتُ بِاللَّهِ رَبَّاً، وَبِالْإِسْلاَمِ دِيناً، وَبِمُحَمَّدٍ صلى الله عليه وسلم نَبِيّاً |ثلاثَ مرَّاتٍ" + }, + "azkarList13": "|لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ |عشرَ مرَّاتٍ", + "@azkarList13": { + "description": "|لاَ إِلَهَ إِلاَّ اللَّهُ وَحْدَهُ لاَ شَرِيكَ لَهُ، لَهُ الْمُلْكُ وَلَهُ الْحَمْدُ، وَهُوَ عَلَى كُلِّ شَيْءٍ قَدِيرٌ |عشرَ مرَّات" + }, "jumuaaScreenTitle": "Czas Jumuaa", "jumuaaHadith": "Prorok ﷺ (niech spoczywa na nim pokój i błogosławieństwo Allaha) powiedział: \"Kto wykonuje doskonale ablucje, a następnie udaje się na jumua, a potem słucha i milczy, temu odpuszcza się to, co jest między tym czasem a następnym piątkiem i trzema kolejnymi dniami, a ten, kto dotyka kamieni, z pewnością popełnił marność\"", "shuruk": "Shuruk", @@ -147,9 +176,181 @@ "mainScreenExplanation": "W przypadku głównego pomieszczenia meczetu, ten ekran nie będzie pokazywał transmisji dżumy na żywo", "normalModeExplanation": "Wyświetla normalny ekran z czasem modlitwy i ogłoszeniami.", "announcementOnlyModeExplanation": "Będzie pokazywać ogłoszenia przez cały czas", + "orientation": "Orientacja", + "selectYourMawaqitTvAppOrientation": "Proszę wybrać orientację aplikacji mawaqit tv", + "deviceDefault": "Domyślne urządzenie", + "deviceDefaultBTNDescription": "Mawaqit automatycznie wybierze domyślną orientację na podstawie orientacji ekranu.", + "portrait": "Portret", + "portraitBTNDescription": "Orientacja pionowa zalecana dla meczetów o małej powierzchni", + "landscape": "Krajobraz", + "landscapeBTNDescription": "Dla orientacji poziomej. Główny układ dla aplikacji mawaqit tv i zalecany dla większości meczetów", "eidMubarak": "Eid Mubarak", "takbeerAleidText": "Allahu Akbar, Allahu Akbar, Allahu Akbar, la ilaha illa Allah, Allahu Akbar, Allahu Akbar, wa lillahi al-hamd", "settings": "Ustawienia", - "applicationModes": "Tryby aplikacji", - "ifYouAreFacingAnIssueWithTheAppActivateThis": "Jeśli napotkają Państwo problem z aplikacją, proszę aktywować tę opcję" + "applicationModes": "Tryb aplikacji", + "ifYouAreFacingAnIssueWithTheAppActivateThis": "Jeśli mają Państwo problemy z aplikacją, proszę spróbować włączyć tę opcję.", + "hijriAdjustments": "Lokalne dostosowania Hijri", + "hijriAdjustmentsDescription": "Dostosuj lokalną datę hijri na urządzeniu. Nie wpłynie to na ustawienia meczetu online", + "backoffice_default": "Domyślne Zapasy", + "recommended": "Rekomendowane", + "sabah": "Poranek", + "randomHadithLanguage": "Losowy język hadith", + "en": "Angielski", + "fr": "Francuski", + "ar": "Arabski", + "tr": "Turecki", + "de": "Niemiecki", + "es": "Hiszpański", + "pt": "Portugalski", + "nl": "Holenderski", + "fr_ar": "Francuski i arabski", + "en_ar": "Angielski i arabski", + "de_ar": "Niemiecki i arabski", + "ta_ar": "Tamil & Arabic", + "tr_ar": "turecki i arabski", + "es_ar": "Hiszpański i arabski", + "pt_ar": "Portugalski i arabski", + "nl_ar": "Holenderski i arabski", + "connectToChangeHadith": "Połącz się z Internetem, aby zmienić język haditów.", + "retry": "Retry", + "timeSetting": "Konfigurowanie czasu", + "timeSettingDesc": "Ustaw niestandardową nazwę", + "selectedTime": "Bieżący wybrany czas", + "confirmation": "Potwierdzenie", + "confirmationMessage": "Czy na pewno chcesz użyć czasu urządzenia?", + "useDeviceTime": "Użyj czasu urządzenia", + "selectTime": "Wybierz czas", + "previous": "Poprzedni", + "appTimezone": "App Timezone", + "descTimezone": "Wybierz strefę czasową, aby uzyskać dokładne czasy modlitwy.", + "appWifi": "Połącz z wifi", + "descWifi": "Połącz się z preferowanym wifi", + "searchCountries": "Szukaj krajów", + "scanAgain": "Skanuj ponownie", + "noScannedResultsFound": "Nie znaleziono bliskich punktów dostępu", + "connect": "Połącz", + "wifiPassword": "Hasło", + "skip": "Pominąć", + "noSSID": "**Hidden SSID**", + "close": "Zamknij", + "search": "Wyszukaj", + "wifiSuccess": "Pomyślnie połączono z Wifi.", + "wifiFailure": "Nie udało się połączyć z Wifi.", + "timezoneSuccess": "Strefa czasowa ustawiona pomyślnie.", + "timezoneFailure": "Nie udało się ustawić strefy czasowej.", + "screenLock": "Ekran włączony/wyłączony", + "screenLockConfig": "Skonfiguruj ekran włączony/wyłączony", + "screenLockMode": "Tryb włączania/wyłączania ekranu", + "screenLockDesc": "Włącz/Wyłącz telewizor przed i po każdej modlitwie, aby zaoszczędzić energię", + "screenLockDesc2": "Ta funkcja włącza/wyłącza urządzenie przed i po każdej modlitwie", + "before": "minuty przed każdą modlitwą", + "after": "minuty po każdej modlitwie", + "updateAvailable": "Dostępna aktualizacja", + "seeMore": "Zobacz więcej", + "whatIsNew": "Co nowego", + "update": "Aktualizacja", + "automaticUpdate": "Powiadom o aktualizacji", + "automaticUpdateDescription": "Włącz aktualizację powiadomień, aby otrzymywać najnowsze funkcje i ulepszenia", + "checkInternetLegacyMode": "Musisz połączyć się z Internetem, aby korzystać z trybu starszego", + "powerOnScreen": "Włącz ekran", + "powerOffScreen": "Wyłącz ekran", + "deviceSettings": "Ustawienia urządzenia", + "later": "Później", + "downloadQuran": "Pobierz Kuran", + "quran": "Kuran", + "askDownloadQuran": "Czy chcesz pobrać Quran?", + "download": "Pobieranie", + "downloadingQuran": "Pobieranie Koranu", + "extractingQuran": "Rozpakowywanie Quran", + "updatedQuran": "Kuran został zaktualizowany", + "quranLatestVersion": "Kuran jest aktualny", + "quranUpdatedVersion": "Zaktualizowana wersja Qurana to: {version}", + "quranIsUpdated": "Kuran został zaktualizowany", + "quranDownloaded": "Pobrano Quran", + "quranIsAlreadyDownloaded": "Kuran jest już pobrany", + "chooseReciter": "Wybierz motyw", + "reciteType": "Recite Type", + "readingMode": "Chcę przeczytać", + "listeningMode": "Chcę słuchać", + "quranReadingPage": "Strona {leftPage} - {rightPage} / {totalPages}", + "@quranReadingPage": { + "description": "Placeholder text for displaying Quran reading page numbers", + "placeholders": { + "leftPage": { + "type": "int", + "example": "1" + }, + "rightPage": { + "type": "int", + "example": "2" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } + }, + "quranReadingPagePortrait": "Strona {currentPage} / {totalPages}", + "@quranReadingPagePortrait": { + "description": "Placeholder text for displaying Quran reading page portrait numbers", + "placeholders": { + "currentPage": { + "type": "int", + "example": "1" + }, + "totalPages": { + "type": "int", + "example": "604" + } + } + }, + "chooseQuranPage": "Wybierz stronę", + "checkingForUpdates": "Sprawdzanie aktualizacji...", + "chooseQuranType": "Wybierz quran", + "hafs": "Kawałki", + "warsh": "Odrzut", + "favorites": "Ulubione", + "allReciters": "Wszystkie Motywy", + "reciterAddedToFavorites": "Reciter {name} dodany do ulubionych", + "@reciterAddedToFavorites": { + "description": "Message shown when a reciter is added to favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Abdul Basit" + } + } + }, + "reciterRemovedFromFavorites": "Usunięto Reciter {name} z ulubionych", + "@reciterRemovedFromFavorites": { + "description": "Message shown when a reciter is removed from favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Abdul Basit" + } + } + }, + "noFavoriteReciters": "Brak ulubionych motywów. Spróbuj dodać jeden do listy", + "@noFavoriteReciters": { + "description": "Message shown when there are no favorite reciters" + }, + "noReciterSearchResult": "Nie znaleziono wyników wyszukiwania", + "searchForReciter": "Szukaj motywu", + "downloadAllSuwarSuccessfully": "Cały quran jest pobierany", + "noSuwarDownload": "Brak nowych suwarów do pobrania", + "connectDownloadQuran": "Połącz się z Internetem, aby pobrać", + "playInOnlineModeQuran": "Połącz się z Internetem, aby zagrać", + "downloaded": "Pobrano", + "switchQuranType": "Przejdź do {name}", + "@switchQuranType": { + "description": "Message shown when a reciter is added to favorites", + "placeholders": { + "name": { + "type": "String", + "example": "Warsh" + } + } + }, + "surahSelector": "Select Surah" } \ No newline at end of file diff --git a/lib/src/const/constants.dart b/lib/src/const/constants.dart index aea70531f..14499c311 100644 --- a/lib/src/const/constants.dart +++ b/lib/src/const/constants.dart @@ -72,7 +72,7 @@ abstract class QuranConstant { static const String kSelectedMoshafType = 'selected_moshaf_type'; static const String kQuranBaseUrl = 'https://mp3quran.net/api/v3/'; static const String kSurahBox = 'surah_box'; - static const String kReciterBox = 'reciter_box'; + static const String kReciterBox = 'reciter_box_v2'; static const String kQuranModePref = 'quran_mode'; static const String kSavedCurrentPage = 'saved_current_page'; static const String kFavoriteReciterBox = 'favorite_reciter_box'; @@ -90,3 +90,9 @@ abstract class AzkarConstant { abstract class SettingsConstant { static const String kLanguageCode = 'language_code'; } + +abstract class SystemFeaturesConstant { + static const String kLeanback = 'android.software.leanback'; + static const String kHdmi = 'android.hardware.hdmi'; + static const String kEthernet = 'android.hardware.ethernet'; +} diff --git a/lib/src/data/data_source/device_info_data_source.dart b/lib/src/data/data_source/device_info_data_source.dart index 30c0d53c5..5f0a54d47 100644 --- a/lib/src/data/data_source/device_info_data_source.dart +++ b/lib/src/data/data_source/device_info_data_source.dart @@ -4,6 +4,7 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:disk_space/disk_space.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mawaqit/src/const/constants.dart'; import 'package:unique_identifier/unique_identifier.dart'; import '../../../main.dart'; @@ -74,20 +75,18 @@ class DeviceInfoDataSource { return Platform.localeName; } - /// [isBoxOrAndroidTV] Checks if the device is a box or a AndroidTV. Future isBoxOrAndroidTV() async { - final features = MethodChannel('nativeMethodsChannel'); + AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; // List of features to check final featuresToCheck = [ - 'android.software.leanback', - 'android.hardware.hdmi', - 'android.hardware.ethernet', + SystemFeaturesConstant.kLeanback, + SystemFeaturesConstant.kHdmi, + SystemFeaturesConstant.kEthernet ]; for (final feature in featuresToCheck) { - final hasFeature = await _checkFeature(features, feature); - if (hasFeature) { + if (androidInfo.systemFeatures.contains(feature)) { return true; } } @@ -95,18 +94,11 @@ class DeviceInfoDataSource { return false; } - /// [_checkFeature] Checks if the device has a specific feature. - Future _checkFeature(MethodChannel features, String feature) async { - try { - final hasFeature = await features.invokeMethod( - 'hasSystemFeature', - {'feature': feature}, - ); - logger.d('hasFeature: $hasFeature $feature'); - return hasFeature != null && hasFeature; - } catch (e) { - return false; - } + /// [isAndroidTv] Checks if the device is AndroidTV. + Future isAndroidTv() async { + AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; + + return androidInfo.systemFeatures.contains(SystemFeaturesConstant.kLeanback); } } diff --git a/lib/src/data/data_source/quran/quran_local_data_source.dart b/lib/src/data/data_source/quran/quran_local_data_source.dart index 415cf93b1..66711cc87 100644 --- a/lib/src/data/data_source/quran/quran_local_data_source.dart +++ b/lib/src/data/data_source/quran/quran_local_data_source.dart @@ -1,16 +1,19 @@ import 'dart:developer'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:fpdart/fpdart.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:mawaqit/src/const/constants.dart'; import 'package:mawaqit/src/domain/model/quran/surah_model.dart'; import 'package:mawaqit/src/domain/error/quran_exceptions.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class QuranLocalDataSource { final Box _surahBox; + final SharedPreferences _prefs; - QuranLocalDataSource(this._surahBox); + QuranLocalDataSource(this._surahBox, this._prefs); /// [saveSuwarByLanguage] save the list of surwars by language code Future saveSuwarByLanguage(String languageCode, List suwar) async { @@ -69,9 +72,28 @@ class QuranLocalDataSource { throw CannotFindSuwarByLanguageException(e.toString()); } } + + Future> getLastUpdateTimestamp(String languageCode) async { + try { + final timestamp = _prefs.getInt('${languageCode}_last_fetch'); + return Option.fromNullable(timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp) : null); + } catch (e) { + log('quran: getLastUpdateTimestamp: ${e.toString()}'); + return None(); + } + } + + Future saveLastUpdateTimestamp(String languageCode, DateTime timestamp) async { + try { + await _prefs.setInt('${languageCode}_last_fetch', timestamp.millisecondsSinceEpoch); + } catch (e) { + throw SaveLastUpdateTimestampException(e.toString()); + } + } } final quranLocalDataSourceProvider = FutureProvider((ref) async { final surahBox = await Hive.openBox(QuranConstant.kSurahBox); - return QuranLocalDataSource(surahBox); + final prefs = await SharedPreferences.getInstance(); + return QuranLocalDataSource(surahBox, prefs); }); diff --git a/lib/src/data/repository/device_info_impl.dart b/lib/src/data/repository/device_info_impl.dart index 7a6806a7b..0fd71fe06 100644 --- a/lib/src/data/repository/device_info_impl.dart +++ b/lib/src/data/repository/device_info_impl.dart @@ -96,6 +96,18 @@ class DeviceInfoImpl implements DeviceInfoRepository { rethrow; } } + + /// [isAndroidTv] Checks if the device androidTV. + /// + /// return a boolean value indicating if the device AndroidTV. + Future isAndroidTv() async { + try { + return await deviceInfoDataSource.isAndroidTv(); + } catch (e, s) { + logger.e('Error fetching device type', stackTrace: s); + rethrow; + } + } } class DeviceInfoImplProviderArgument { diff --git a/lib/src/data/repository/quran/quran_impl.dart b/lib/src/data/repository/quran/quran_impl.dart index b8dd6e7f7..98a367991 100644 --- a/lib/src/data/repository/quran/quran_impl.dart +++ b/lib/src/data/repository/quran/quran_impl.dart @@ -10,6 +10,7 @@ import 'package:mawaqit/src/data/data_source/quran/quran_remote_data_source.dart class QuranImpl extends QuranRepository { final QuranRemoteDataSource _quranRemoteDataSource; final QuranLocalDataSource _quranLocalDataSource; + final Duration _cacheValidityDuration = Duration(days: 30); QuranImpl( this._quranRemoteDataSource, @@ -28,15 +29,43 @@ class QuranImpl extends QuranRepository { String languageCode = 'en', }) async { try { + // Check if cached data exists and is still valid + if (await _isCacheValid(languageCode)) { + final cachedSuwar = await _quranLocalDataSource.getSuwarByLanguage(languageCode); + return cachedSuwar; + } + + // If cache is invalid or doesn't exist, fetch from remote final suwar = await _quranRemoteDataSource.getSuwarByLanguage(languageCode: languageCode); - log('quran: QuranImpl: getSuwarByLanguage: ${suwar[0]}'); - await _quranLocalDataSource.saveSuwarByLanguage(languageCode, suwar); + + // Save the new data to cache with current timestamp + await _saveSuwarWithTimestamp(languageCode, suwar); + return suwar; } on Exception catch (_) { - final suwar = await _quranLocalDataSource.getSuwarByLanguage(languageCode); - return suwar; + // If remote fetch fails, try to return cached data even if it's outdated + final cachedSuwar = await _quranLocalDataSource.getSuwarByLanguage(languageCode); + if (cachedSuwar.isNotEmpty) { + return cachedSuwar; + } + // If no cached data, rethrow the exception + rethrow; } } + + Future _isCacheValid(String languageCode) async { + final lastUpdateTimestamp = await _quranLocalDataSource.getLastUpdateTimestamp(languageCode); + return lastUpdateTimestamp.fold(() => false, (time) { + final currentTime = DateTime.now(); + final difference = currentTime.difference(time); + return difference < _cacheValidityDuration; + }); + } + + Future _saveSuwarWithTimestamp(String languageCode, List suwar) async { + await _quranLocalDataSource.saveSuwarByLanguage(languageCode, suwar); + await _quranLocalDataSource.saveLastUpdateTimestamp(languageCode, DateTime.now()); + } } final quranRepositoryProvider = FutureProvider((ref) async { diff --git a/lib/src/domain/error/quran_exceptions.dart b/lib/src/domain/error/quran_exceptions.dart index facb60b51..1bf25f2a8 100644 --- a/lib/src/domain/error/quran_exceptions.dart +++ b/lib/src/domain/error/quran_exceptions.dart @@ -103,3 +103,8 @@ class CannotFindSuwarByLanguageException extends QuranException { CannotFindSuwarByLanguageException(String message) : super('Error occurred while finding suwar by language: $message', 'FIND_SUWAR_BY_LANGUAGE_ERROR'); } + +class SaveLastUpdateTimestampException extends QuranException { + SaveLastUpdateTimestampException(String message) + : super('Error occurred while saving last update timestamp: $message', 'SAVE_LAST_UPDATE_TIMESTAMP_ERROR'); +} diff --git a/lib/src/domain/repository/device_repository.dart b/lib/src/domain/repository/device_repository.dart index 692dd2a7e..d6b3a1a0e 100644 --- a/lib/src/domain/repository/device_repository.dart +++ b/lib/src/domain/repository/device_repository.dart @@ -17,4 +17,7 @@ abstract class DeviceInfoRepository { /// [isBoxOrAndroidTV] Checks if the device is a box or a androidTV. Future isBoxOrAndroidTV(); + + /// [isAndroidTv] Checks if the device androidTV. + Future isAndroidTv(); } diff --git a/lib/src/pages/quran/page/quran_player_screen.dart b/lib/src/pages/quran/page/quran_player_screen.dart index df3083d0b..9d077b31c 100644 --- a/lib/src/pages/quran/page/quran_player_screen.dart +++ b/lib/src/pages/quran/page/quran_player_screen.dart @@ -1,13 +1,16 @@ -import 'dart:developer'; import 'dart:math' as math; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:fpdart/fpdart.dart'; +import 'package:mawaqit/src/const/constants.dart'; import 'package:mawaqit/src/domain/model/quran/moshaf_model.dart'; import 'package:mawaqit/src/domain/model/quran/surah_model.dart'; +import 'package:mawaqit/src/helpers/RelativeSizes.dart'; import 'package:mawaqit/src/pages/quran/page/surah_selection_screen.dart'; import 'package:mawaqit/src/pages/quran/widget/quran_player/seek_bar.dart'; import 'package:mawaqit/src/state_management/quran/recite/download_audio_quran/download_audio_quran_notifier.dart'; @@ -91,11 +94,19 @@ class _QuranPlayerScreenState extends ConsumerState { return const SizedBox(); }, data: (quranPlayerState) { - return Stack( - fit: StackFit.expand, + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const _BackgroundFilter(), - Positioned( + // const _BackgroundFilter(), + Flexible( + flex: 3, + child: Align( + alignment: Alignment.topCenter, + child: buildReciterImage(), + ), + ), + Flexible( + flex: 9, child: _QuranPlayer( backButtonFocusNode: backButtonFocusNode, isPlaying: quranPlayerState.playerState == AudioPlayerState.playing, @@ -107,9 +118,6 @@ class _QuranPlayerScreenState extends ConsumerState { reciterId: widget.reciterId, surah: widget.surah, ), - bottom: 0, - left: 0, - right: 0, ), ], ); @@ -118,6 +126,23 @@ class _QuranPlayerScreenState extends ConsumerState { ), ); } + + ClipOval buildReciterImage() { + return ClipOval( + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + ), + child: CachedNetworkImage( + imageUrl: '${QuranConstant.kQuranReciterImagesBaseUrl}${widget.reciterId}.jpg', + fit: BoxFit.fitWidth, + placeholder: (context, url) => Container(color: Colors.transparent), + errorWidget: (context, url, error) => Container(color: Colors.transparent), + ), + ), + ); + } } class _QuranPlayer extends ConsumerStatefulWidget { @@ -211,447 +236,461 @@ class _QuranPlayerState extends ConsumerState<_QuranPlayer> { final directionality = Directionality.of(context); final theme = Theme.of(context); return Padding( - padding: EdgeInsets.all(3.w), + padding: EdgeInsets.all(1.h), child: FocusTraversalGroup( policy: OrderedTraversalPolicy(), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - widget.surahName, - style: TextStyle( - fontSize: 5.w, - fontWeight: FontWeight.bold, - color: Colors.white, + // Add reciter's image here + // SizedBox(height: 1.h), + FittedBox( + child: Text( + widget.surahName, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), ), SizedBox(height: 1.h), - Text( - widget.surahType, - style: TextStyle( - fontSize: 4.w, - color: Colors.grey[400], + FittedBox( + child: Text( + widget.surahType, + style: TextStyle( + fontSize: 14.sp, + color: Colors.grey[400], + ), ), ), - SizedBox(height: 4.h), - Row( + SizedBox(height: 1.h), + buildDownloadButton(), + buildSlider(), + SizedBox(height: 2.h), + buildBottom(quranState, theme, directionality, context), + ], + ), + ), + ); + } + + Row buildDownloadButton() { + return Row( + children: [ + Spacer(), + Consumer( + builder: (context, ref, child) { + final quranPlayer = ref.watch( + downloadStateProvider( + DownloadStateProviderParameter( + reciterId: widget.reciterId, + moshafId: widget.selectedMoshaf.id.toString(), + ), + ), + ); + + final isDownloaded = quranPlayer.downloadedSuwar.contains(widget.surah.id); + + final findFirstDownloadedSurah = quranPlayer.downloadingSuwar.firstWhereOrNull( + (element) => element.surahId == widget.surah.id, + ); + return downloadingWidget( + isDownloaded, + Option.fromNullable(findFirstDownloadedSurah), + ); + }, + ), + ], + ); + } + + FocusTraversalOrder buildBottom(AsyncValue quranState, ThemeData theme, + TextDirection directionality, BuildContext context) { + return FocusTraversalOrder( + order: NumericFocusOrder(1), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: Row( children: [ - Spacer(), - Consumer( - builder: (context, ref, child) { - final quranPlayer = ref.watch( - downloadStateProvider( - DownloadStateProviderParameter( - reciterId: widget.reciterId, - moshafId: widget.selectedMoshaf.id.toString(), + quranState.maybeWhen( + orElse: () => const SizedBox(), + data: (data) { + return FocusableActionDetector( + focusNode: repeatFocusNode, + onFocusChange: (hasFocus) { + setState(() {}); + }, + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), + }, + actions: { + ActivateIntent: CallbackAction( + onInvoke: (ActivateIntent intent) { + ref.read(quranPlayerNotifierProvider.notifier).repeat(); + return null; + }, + ), + }, + child: Container( + decoration: BoxDecoration( + color: repeatFocusNode.hasFocus ? theme.primaryColor : Colors.transparent, + shape: BoxShape.circle, + ), + child: IconButton( + icon: SvgPicture.asset( + R.ASSETS_ICON_REPEAT_SVG, + color: data.isRepeating || repeatFocusNode.hasFocus ? Colors.white : Colors.grey[800], + width: 6.w, + ), + iconSize: 8.w, + onPressed: () { + ref.read(quranPlayerNotifierProvider.notifier).repeat(); + repeatFocusNode.requestFocus(); + }, ), ), ); - - final isDownloaded = quranPlayer.downloadedSuwar.contains(widget.surah.id); - - final findFirstDownloadedSurah = quranPlayer.downloadingSuwar.firstWhereOrNull( - (element) => element.surahId == widget.surah.id, - ); - return downloadingWidget( - isDownloaded, - Option.fromNullable(findFirstDownloadedSurah), - ); }, ), - ], - ), - StreamBuilder( - stream: widget._seekBarDataStream, - builder: (context, snapshot) { - final position = snapshot.data?.position ?? Duration.zero; - final duration = snapshot.data?.duration ?? Duration.zero; - return Column( - children: [ - FocusTraversalOrder( - order: NumericFocusOrder(0), - child: SliderTheme( - data: SliderTheme.of(context).copyWith( - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 7, - ), - overlayShape: const RoundSliderOverlayShape( - overlayRadius: 10, - ), - thumbColor: Colors.white, - valueIndicatorShape: const PaddleSliderValueIndicatorShape(), - ), - child: MediaQuery( - data: MediaQueryData( - navigationMode: NavigationMode.directional, - ), - child: SliderTheme( - data: SliderThemeData( - thumbColor: _sliderThumbColor, - ), - child: Slider( - focusNode: sliderFocusNode, - value: position.inSeconds.toDouble(), - max: duration.inSeconds.toDouble(), - onChanged: (value) { - ref.read(quranPlayerNotifierProvider.notifier).seekTo(Duration(seconds: value.toInt())); - }, - ), - ), + quranState.maybeWhen( + orElse: () => const SizedBox(), + data: (data) { + return FocusableActionDetector( + focusNode: shuffleFocusNode, + actions: >{ + ActivateIntent: CallbackAction( + onInvoke: (ActivateIntent intent) { + ref.read(quranPlayerNotifierProvider.notifier).shuffle(); + return null; + }, ), - ), - ), - SizedBox(height: 2.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - position.toString().split('.').first, - style: TextStyle( - fontSize: 12.sp, - color: Color(0xFFA8A8A8), - fontWeight: FontWeight.w500, - ), + }, + onFocusChange: (hasFocus) { + setState(() {}); + }, + child: Container( + decoration: BoxDecoration( + color: shuffleFocusNode.hasFocus ? theme.primaryColor : Colors.transparent, + shape: BoxShape.circle, ), - Text( - duration.toString().split('.').first, - style: TextStyle( - fontSize: 12.sp, - color: Color(0xFFA8A8A8), - fontWeight: FontWeight.w500, + child: IconButton( + icon: SvgPicture.asset( + R.ASSETS_ICON_SHUFFLE_SVG, + color: data.isShuffled || shuffleFocusNode.hasFocus ? Colors.white : Colors.grey[800], + matchTextDirection: true, + width: 6.w, ), + iconSize: 8.w, + onPressed: () { + ref.read(quranPlayerNotifierProvider.notifier).shuffle(); + shuffleFocusNode.requestFocus(); + }, ), - ], + ), + ); + }, + ), + ], + ), + ), + InkWell( + child: Builder( + builder: (context) { + final isFocused = Focus.of(context).hasFocus; + return Container( + decoration: BoxDecoration( + color: isFocused ? theme.primaryColor : Colors.transparent, + shape: BoxShape.circle, + ), + child: IconButton( + icon: SvgPicture.asset( + directionality != TextDirection.ltr + ? R.ASSETS_ICON_SKIP_NEXT_SVG + : R.ASSETS_ICON_SKIP_PREVIOUS_SVG, + color: Colors.white, + width: 6.w, ), - ], + iconSize: 8.w, + onPressed: () { + final notifier = ref.read(quranPlayerNotifierProvider.notifier); + notifier.seekToPrevious(); + }, + ), ); }, ), - SizedBox(height: 4.h), - FocusTraversalOrder( - order: NumericFocusOrder(1), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded( - flex: 2, - child: Row( - children: [ - quranState.maybeWhen( - orElse: () => const SizedBox(), - data: (data) { - return FocusableActionDetector( - focusNode: repeatFocusNode, - onFocusChange: (hasFocus) { - setState(() {}); - }, - shortcuts: { - LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), - }, - actions: { - ActivateIntent: CallbackAction( - onInvoke: (ActivateIntent intent) { - ref.read(quranPlayerNotifierProvider.notifier).repeat(); - return null; - }, - ), - }, - child: Container( - decoration: BoxDecoration( - color: repeatFocusNode.hasFocus ? theme.primaryColor : Colors.transparent, - shape: BoxShape.circle, - ), - child: IconButton( - icon: SvgPicture.asset( - R.ASSETS_ICON_REPEAT_SVG, - color: - data.isRepeating || repeatFocusNode.hasFocus ? Colors.white : Colors.grey[800], - width: 6.w, - ), - iconSize: 8.w, - onPressed: () { - ref.read(quranPlayerNotifierProvider.notifier).repeat(); - repeatFocusNode.requestFocus(); - }, - ), - ), - ); - }, - ), - quranState.maybeWhen( - orElse: () => const SizedBox(), - data: (data) { - return FocusableActionDetector( - focusNode: shuffleFocusNode, - actions: >{ - ActivateIntent: CallbackAction( - onInvoke: (ActivateIntent intent) { - ref.read(quranPlayerNotifierProvider.notifier).shuffle(); - return null; - }, - ), - }, - onFocusChange: (hasFocus) { - setState(() {}); - }, - child: Container( - decoration: BoxDecoration( - color: shuffleFocusNode.hasFocus ? theme.primaryColor : Colors.transparent, - shape: BoxShape.circle, - ), - child: IconButton( - icon: SvgPicture.asset( - R.ASSETS_ICON_SHUFFLE_SVG, - color: - data.isShuffled || shuffleFocusNode.hasFocus ? Colors.white : Colors.grey[800], - matchTextDirection: true, - width: 6.w, - ), - iconSize: 8.w, - onPressed: () { - ref.read(quranPlayerNotifierProvider.notifier).shuffle(); - shuffleFocusNode.requestFocus(); - }, - ), - ), - ); - }, - ), - ], - ), + ), + InkWell( + child: Builder( + builder: (context) { + final isFocused = Focus.of(context).hasFocus; + return Container( + decoration: BoxDecoration( + color: isFocused ? theme.primaryColor : Colors.transparent, + shape: BoxShape.circle, ), - InkWell( - child: Builder( - builder: (context) { - final isFocused = Focus.of(context).hasFocus; - return Container( - decoration: BoxDecoration( - color: isFocused ? theme.primaryColor : Colors.transparent, - shape: BoxShape.circle, - ), - child: IconButton( - icon: SvgPicture.asset( - directionality != TextDirection.ltr - ? R.ASSETS_ICON_SKIP_NEXT_SVG - : R.ASSETS_ICON_SKIP_PREVIOUS_SVG, + child: IconButton( + icon: widget.isPlaying + ? SvgPicture.asset( + R.ASSETS_ICON_PAUSE_SVG, + color: Colors.white, + ) + : Transform.rotate( + angle: directionality == TextDirection.rtl ? math.pi : 0, + child: Icon( + Icons.play_arrow, color: Colors.white, - width: 6.w, + size: 8.w, ), - iconSize: 8.w, - onPressed: () { - final notifier = ref.read(quranPlayerNotifierProvider.notifier); - notifier.seekToPrevious(); - }, ), - ); - }, + iconSize: 10.w, + onPressed: () { + final notifier = ref.read(quranPlayerNotifierProvider.notifier); + if (widget.isPlaying) { + notifier.pause(); + } else { + notifier.play(); + } + }, + ), + ); + }, + ), + ), + InkWell( + child: Builder( + builder: (context) { + final isFocused = Focus.of(context).hasFocus; + return Container( + decoration: BoxDecoration( + color: isFocused ? theme.primaryColor : Colors.transparent, + shape: BoxShape.circle, + ), + child: IconButton( + icon: SvgPicture.asset( + directionality == TextDirection.ltr + ? R.ASSETS_ICON_SKIP_NEXT_SVG + : R.ASSETS_ICON_SKIP_PREVIOUS_SVG, + color: Colors.white, + width: 6.w, ), + iconSize: 8.w, + onPressed: () { + final notifier = ref.read(quranPlayerNotifierProvider.notifier); + notifier.seekToNext(); + }, ), - InkWell( - child: Builder( - builder: (context) { - final isFocused = Focus.of(context).hasFocus; - return Container( - decoration: BoxDecoration( - color: isFocused ? theme.primaryColor : Colors.transparent, - shape: BoxShape.circle, - ), - child: IconButton( - icon: widget.isPlaying - ? SvgPicture.asset( - R.ASSETS_ICON_PAUSE_SVG, - color: Colors.white, - ) - : Transform.rotate( - angle: directionality == TextDirection.rtl ? math.pi : 0, - child: Icon( - Icons.play_arrow, - color: Colors.white, - size: 8.w, - ), - ), - iconSize: 10.w, - onPressed: () { - final notifier = ref.read(quranPlayerNotifierProvider.notifier); - if (widget.isPlaying) { - notifier.pause(); + ); + }, + ), + ), + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FocusableActionDetector( + focusNode: volumeFocusNode, + onFocusChange: (hasFocus) { + if (!hasFocus) { + ref.read(quranPlayerNotifierProvider.notifier).closeVolume(); + } + setState(() {}); + }, + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), + LogicalKeySet(LogicalKeyboardKey.arrowLeft): const DirectionalFocusIntent(TraversalDirection.left), + LogicalKeySet(LogicalKeyboardKey.arrowUp): const DirectionalFocusIntent(TraversalDirection.up), + LogicalKeySet(LogicalKeyboardKey.arrowDown): const DirectionalFocusIntent(TraversalDirection.down), + LogicalKeySet(LogicalKeyboardKey.arrowRight): + const DirectionalFocusIntent(TraversalDirection.right), + }, + actions: { + DirectionalFocusIntent: CallbackAction( + onInvoke: (DirectionalFocusIntent intent) { + final quranNotifier = ref.read(quranPlayerNotifierProvider.notifier); + final isRTL = Directionality.of(context) == TextDirection.rtl; + quranState.maybeWhen( + orElse: () {}, + data: (state) { + if (state.isVolumeOpened) { + switch (intent.direction) { + case TraversalDirection.left: + if (isRTL) { + quranNotifier.setVolume(state.volume + 0.1); + } else { + quranNotifier.setVolume(state.volume - 0.1); + } + break; + case TraversalDirection.right: + if (isRTL) { + quranNotifier.setVolume(state.volume - 0.1); + } else { + quranNotifier.setVolume(state.volume + 0.1); + } + break; + case TraversalDirection.up: + sliderFocusNode.requestFocus(); + break; + case TraversalDirection.down: + playFocusNode.requestFocus(); + break; + } } else { - notifier.play(); + switch (intent.direction) { + case TraversalDirection.up: + sliderFocusNode.requestFocus(); + break; + case TraversalDirection.down: + playFocusNode.requestFocus(); + break; + case TraversalDirection.left: + if (!isRTL) { + playFocusNode.requestFocus(); + } + break; + case TraversalDirection.right: + if (isRTL) { + playFocusNode.requestFocus(); + } + break; + } } - }, - ), - ); + }); + return null; }, ), - ), - InkWell( - child: Builder( - builder: (context) { - final isFocused = Focus.of(context).hasFocus; - return Container( - decoration: BoxDecoration( - color: isFocused ? theme.primaryColor : Colors.transparent, - shape: BoxShape.circle, - ), - child: IconButton( - icon: SvgPicture.asset( - directionality == TextDirection.ltr - ? R.ASSETS_ICON_SKIP_NEXT_SVG - : R.ASSETS_ICON_SKIP_PREVIOUS_SVG, - color: Colors.white, - width: 6.w, - ), - iconSize: 8.w, - onPressed: () { - final notifier = ref.read(quranPlayerNotifierProvider.notifier); - notifier.seekToNext(); - }, - ), - ); + ActivateIntent: CallbackAction( + onInvoke: (ActivateIntent intent) { + final quranNotifier = ref.read(quranPlayerNotifierProvider.notifier); + quranNotifier.toggleVolume(); + return null; }, ), - ), - Expanded( - flex: 2, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FocusableActionDetector( - focusNode: volumeFocusNode, - onFocusChange: (hasFocus) { - if (!hasFocus) { - ref.read(quranPlayerNotifierProvider.notifier).closeVolume(); - } - setState(() {}); - }, - shortcuts: { - LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(), - LogicalKeySet(LogicalKeyboardKey.arrowLeft): - const DirectionalFocusIntent(TraversalDirection.left), - LogicalKeySet(LogicalKeyboardKey.arrowUp): - const DirectionalFocusIntent(TraversalDirection.up), - LogicalKeySet(LogicalKeyboardKey.arrowDown): - const DirectionalFocusIntent(TraversalDirection.down), - LogicalKeySet(LogicalKeyboardKey.arrowRight): - const DirectionalFocusIntent(TraversalDirection.right), - }, - actions: { - DirectionalFocusIntent: CallbackAction( - onInvoke: (DirectionalFocusIntent intent) { - final quranNotifier = ref.read(quranPlayerNotifierProvider.notifier); - final isRTL = Directionality.of(context) == TextDirection.rtl; - quranState.maybeWhen( - orElse: () {}, - data: (state) { - if (state.isVolumeOpened) { - switch (intent.direction) { - case TraversalDirection.left: - if (isRTL) { - quranNotifier.setVolume(state.volume + 0.1); - } else { - quranNotifier.setVolume(state.volume - 0.1); - } - break; - case TraversalDirection.right: - if (isRTL) { - quranNotifier.setVolume(state.volume - 0.1); - } else { - quranNotifier.setVolume(state.volume + 0.1); - } - break; - case TraversalDirection.up: - sliderFocusNode.requestFocus(); - break; - case TraversalDirection.down: - playFocusNode.requestFocus(); - break; - } - } else { - switch (intent.direction) { - case TraversalDirection.up: - sliderFocusNode.requestFocus(); - break; - case TraversalDirection.down: - playFocusNode.requestFocus(); - break; - case TraversalDirection.left: - if (!isRTL) { - playFocusNode.requestFocus(); - } - break; - case TraversalDirection.right: - if (isRTL) { - playFocusNode.requestFocus(); - } - break; - } - } - }); - return null; - }, - ), - ActivateIntent: CallbackAction( - onInvoke: (ActivateIntent intent) { - final quranNotifier = ref.read(quranPlayerNotifierProvider.notifier); - quranNotifier.toggleVolume(); - return null; + }, + child: Consumer( + builder: (context, ref, child) { + final playerState = ref.watch(quranPlayerNotifierProvider); + final isRTL = Directionality.of(context) == TextDirection.rtl; + return playerState.when( + data: (state) { + if (state.isVolumeOpened && volumeFocusNode.hasFocus) { + return Slider( + thumbColor: _volumeSliderThumbColor, + value: state.volume, + onChanged: (newValue) { + ref.read(quranPlayerNotifierProvider.notifier).setVolume(newValue); }, - ), - }, - child: Consumer( - builder: (context, ref, child) { - final playerState = ref.watch(quranPlayerNotifierProvider); - final isRTL = Directionality.of(context) == TextDirection.rtl; - return playerState.when( - data: (state) { - if (state.isVolumeOpened && volumeFocusNode.hasFocus) { - return Slider( - thumbColor: _volumeSliderThumbColor, - value: state.volume, - onChanged: (newValue) { - ref.read(quranPlayerNotifierProvider.notifier).setVolume(newValue); - }, - min: 0.0, - max: 1.0, - ); - } else { - return Container( - decoration: BoxDecoration( - color: volumeFocusNode.hasFocus ? theme.primaryColor : Colors.transparent, - shape: BoxShape.circle, - ), - child: IconButton( - iconSize: 8.w, - icon: Transform.scale( - scaleX: isRTL ? -1 : 1, - child: Icon( - Icons.volume_down_rounded, - size: 18.sp, - ), - ), - onPressed: () { - volumeFocusNode.requestFocus(); - ref.read(quranPlayerNotifierProvider.notifier).toggleVolume(); - }, - ), - ); - } + min: 0.0, + max: 1.0, + ); + } else { + return Container( + decoration: BoxDecoration( + color: volumeFocusNode.hasFocus ? theme.primaryColor : Colors.transparent, + shape: BoxShape.circle, + ), + child: IconButton( + iconSize: 8.w, + icon: Transform.scale( + scaleX: isRTL ? -1 : 1, + child: Icon( + Icons.volume_down_rounded, + size: 18.sp, + ), + ), + onPressed: () { + volumeFocusNode.requestFocus(); + ref.read(quranPlayerNotifierProvider.notifier).toggleVolume(); }, - loading: () => CircularProgressIndicator(), - error: (error, stack) => Text('Error: $error'), - ); - }, - ), - ), - ], + ), + ); + } + }, + loading: () => CircularProgressIndicator(), + error: (error, stack) => Text('Error: $error'), + ); + }, + ), + ), + ], + ), + ), + ], + ), + ); + } + + StreamBuilder buildSlider() { + return StreamBuilder( + stream: widget._seekBarDataStream, + builder: (context, snapshot) { + final position = snapshot.data?.position ?? Duration.zero; + final duration = snapshot.data?.duration ?? Duration.zero; + return Column( + children: [ + FocusTraversalOrder( + order: NumericFocusOrder(0), + child: SliderTheme( + data: SliderTheme.of(context).copyWith( + thumbShape: RoundSliderThumbShape( + enabledThumbRadius: 7.vwr, + ), + overlayShape: RoundSliderOverlayShape( + overlayRadius: 10.vwr, + ), + thumbColor: Colors.white, + valueIndicatorShape: const PaddleSliderValueIndicatorShape(), + ), + child: MediaQuery( + data: MediaQueryData( + navigationMode: NavigationMode.directional, + ), + child: SliderTheme( + data: SliderThemeData( + thumbColor: _sliderThumbColor, + ), + child: Slider( + focusNode: sliderFocusNode, + value: position.inSeconds.toDouble(), + max: duration.inSeconds.toDouble(), + onChanged: (value) { + ref.read(quranPlayerNotifierProvider.notifier).seekTo(Duration(seconds: value.toInt())); + }, ), ), - ], + ), ), ), + SizedBox(height: 2.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + position.toString().split('.').first, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xFFA8A8A8), + fontWeight: FontWeight.w500, + ), + ), + Text( + duration.toString().split('.').first, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xFFA8A8A8), + fontWeight: FontWeight.w500, + ), + ), + ], + ), ], - ), - ), + ); + }, ); } diff --git a/lib/src/pages/quran/page/reciter_selection_screen.dart b/lib/src/pages/quran/page/reciter_selection_screen.dart index 60eb2407b..60ca0af4f 100644 --- a/lib/src/pages/quran/page/reciter_selection_screen.dart +++ b/lib/src/pages/quran/page/reciter_selection_screen.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; @@ -7,7 +8,6 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:mawaqit/const/resource.dart'; -import 'package:mawaqit/src/pages/quran/reading/quran_reading_screen.dart'; import 'package:mawaqit/src/pages/quran/widget/recite_type_grid_view.dart'; import 'package:mawaqit/src/services/theme_manager.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; @@ -21,6 +21,7 @@ import 'package:mawaqit/i18n/l10n.dart'; import 'package:mawaqit/src/pages/quran/widget/reciter_list_view.dart'; import '../../../domain/model/quran/reciter_model.dart'; +import '../reading/quran_reading_screen.dart'; class ReciterSelectionScreen extends ConsumerStatefulWidget { final String surahName; @@ -137,13 +138,17 @@ class _ReciterSelectionScreenState extends ConsumerState toolbarHeight: 40, backgroundColor: Color(0xFF28262F), elevation: 0, - title: Text( + title: AutoSizeText( S.of(context).chooseReciter, style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.bold, ), + maxLines: 1, + minFontSize: 6.sp.roundToDouble(), + maxFontSize: 20.sp.roundToDouble(), + stepGranularity: 1, ), actions: [ Consumer( diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 9796c39c2..272895dff 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mawaqit/i18n/l10n.dart'; +import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; import 'package:mawaqit/src/pages/quran/reading/widget/quran_floating_action_buttons.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_reading_widgets.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_surah_selector.dart'; @@ -12,6 +13,8 @@ import 'package:mawaqit/src/pages/quran/widget/reading/quran_surah_selector.dart import 'package:mawaqit/src/services/user_preferences_manager.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/quran/quran_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; @@ -24,6 +27,8 @@ import 'package:sizer/sizer.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_reading_page_selector.dart'; +import '../../../data/data_source/device_info_data_source.dart'; + class QuranReadingScreen extends ConsumerStatefulWidget { const QuranReadingScreen({super.key}); @@ -42,7 +47,6 @@ class _QuranReadingScreenState extends ConsumerState { late FocusNode _portraitModeBackButtonFocusNode; late FocusNode _portraitModeSwitchQuranFocusNode; late FocusNode _portraitModePageSelectorFocusNode; - final ScrollController _gridScrollController = ScrollController(); final GlobalKey _scaffoldKey = GlobalKey(); @@ -70,9 +74,12 @@ class _QuranReadingScreenState extends ConsumerState { _switchToPlayQuranFocusNode = FocusNode(debugLabel: 'switch_to_play_quran_node'); } + bool _isRotated = false; + @override void dispose() { _disposeFocusNodes(); + super.dispose(); } @@ -88,12 +95,10 @@ class _QuranReadingScreenState extends ConsumerState { _switchToPlayQuranFocusNode.dispose(); } - void _toggleOrientation(UserPreferencesManager userPrefs) { - final newOrientation = - MediaQuery.of(context).orientation == Orientation.portrait ? Orientation.landscape : Orientation.portrait; - - userPrefs.orientationLandscape = newOrientation == Orientation.landscape; - setState(() {}); + void _toggleOrientation() { + setState(() { + _isRotated = !_isRotated; + }); } @override @@ -129,36 +134,40 @@ class _QuranReadingScreenState extends ConsumerState { _leftSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); _rightSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); - _switchQuranModeNode.onKeyEvent = (node, event) => _handlePageScrollDownFocusGroupNode(node, event); - _switchScreenViewFocusNode.onKeyEvent = (node, event) => _handlePageScrollDownFocusGroupNode(node, event); - _portraitModeBackButtonFocusNode.onKeyEvent = (node, event) => _handlePageScrollUpFocusGroupNode(node, event); - _portraitModeSwitchQuranFocusNode.onKeyEvent = (node, event) => _handlePageScrollUpFocusGroupNode(node, event); - _portraitModePageSelectorFocusNode.onKeyEvent = (node, event) => _handlePageScrollDownFocusGroupNode(node, event); + _switchScreenViewFocusNode.onKeyEvent = + (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); + _portraitModePageSelectorFocusNode.onKeyEvent = + (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); + _switchQuranModeNode.onKeyEvent = (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); + _portraitModeBackButtonFocusNode.onKeyEvent = + (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated); + _portraitModeSwitchQuranFocusNode.onKeyEvent = + (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated); final autoReadingState = ref.watch(autoScrollNotifierProvider); - return OrientationBuilder( - builder: (context, orientation) { - final isPortrait = orientation == Orientation.portrait; - - return WillPopScope( - onWillPop: () async { - userPrefs.orientationLandscape = true; - return true; - }, - child: Scaffold( - backgroundColor: Colors.white, - floatingActionButtonLocation: _getFloatingActionButtonLocation(context), - floatingActionButton: QuranFloatingActionControls( - switchScreenViewFocusNode: _switchScreenViewFocusNode, - switchQuranModeNode: _switchQuranModeNode, - switchToPlayQuranFocusNode: _switchToPlayQuranFocusNode, + return WillPopScope( + onWillPop: () async { + userPrefs.orientationLandscape = true; + return true; + }, + child: RotatedBox( + quarterTurns: _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, _isRotated, userPrefs, autoReadingState), ), - body: _buildBody(quranReadingState, isPortrait, userPrefs, autoReadingState), ), - ); - }, - ); + )); } Widget _buildBody( @@ -188,8 +197,15 @@ class _QuranReadingScreenState extends ConsumerState { : buildHorizontalPageView(quranReadingState, ref, context), if (!isPortrait) ...[ buildRightSwitchButton( - context, _rightSkipButtonFocusNode, () => _scrollPageList(ScrollDirection.forward)), - buildLeftSwitchButton(context, _leftSkipButtonFocusNode, () => _scrollPageList(ScrollDirection.reverse)), + context, + _rightSkipButtonFocusNode, + () => _scrollPageList(ScrollDirection.forward, isPortrait), + ), + buildLeftSwitchButton( + context, + _leftSkipButtonFocusNode, + () => _scrollPageList(ScrollDirection.reverse, isPortrait), + ), ], buildPageNumberIndicator( quranReadingState, isPortrait, context, _portraitModePageSelectorFocusNode, _showPageSelector), @@ -200,7 +216,7 @@ class _QuranReadingScreenState extends ConsumerState { _isThereCurrentDialogShowing(context)), buildBackButton( isPortrait, userPrefs, context, isPortrait ? _portraitModeBackButtonFocusNode : _backButtonFocusNode), - isPortrait ? Container() : buildShowSurah(quranReadingState), + isPortrait ? SizedBox() : buildShowSurah(quranReadingState), ], ); }, @@ -211,7 +227,7 @@ class _QuranReadingScreenState extends ConsumerState { return Align( alignment: Alignment.topCenter, child: Padding( - padding: EdgeInsets.only(top: 0.5.h), + padding: EdgeInsets.only(top: 0.3.h), child: Material( color: Colors.transparent, child: InkWell( @@ -221,7 +237,7 @@ class _QuranReadingScreenState extends ConsumerState { }, borderRadius: BorderRadius.circular(20), child: Container( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), decoration: BoxDecoration( color: Colors.black.withOpacity(0.4), borderRadius: BorderRadius.circular(20), @@ -250,7 +266,6 @@ class _QuranReadingScreenState extends ConsumerState { controller: autoScrollState.scrollController, itemCount: quranReadingState.totalPages, itemBuilder: (context, index) { - return LayoutBuilder( builder: (context, constraints) { final pageHeight = @@ -266,19 +281,92 @@ class _QuranReadingScreenState extends ConsumerState { ); } - void _scrollPageList(ScrollDirection direction) { - final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; + Widget buildFloatingPortrait(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { + return Padding( + padding: EdgeInsets.only(left: 30), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + _buildOrientationToggleButton(isPortrait), + _buildQuranModeButton(isPortrait, userPrefs, context), + ], + ), + ); + } + + Widget buildFloatingLandscape(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _buildOrientationToggleButton(isPortrait), + SizedBox(height: 10), + _buildQuranModeButton(isPortrait, userPrefs, context), + ], + ); + } + + Widget _buildOrientationToggleButton(bool isPortrait) { + return SizedBox( + width: isPortrait ? 35.sp : 30.sp, + height: isPortrait ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: _switchScreenViewFocusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + !isPortrait ? Icons.stay_current_portrait : Icons.stay_current_landscape, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, + ), + onPressed: () => _toggleOrientation(), + heroTag: null, + ), + ); + } + + Widget _buildQuranModeButton(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { + return SizedBox( + width: isPortrait ? 35.sp : 30.sp, + height: isPortrait ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: _switchQuranModeNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + Icons.headset, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, + ), + onPressed: () async { + ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); + if (isPortrait) { + userPrefs.orientationLandscape = true; + } + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => ReciterSelectionScreen.withoutSurahName(), + ), + ); + }, + heroTag: null, + ), + ); + } + + void _scrollPageList(ScrollDirection direction, isPortrait) { if (direction == ScrollDirection.forward) { ref.read(quranReadingNotifierProvider.notifier).previousPage(isPortrait: isPortrait); } else { ref.read(quranReadingNotifierProvider.notifier).nextPage(isPortrait: isPortrait); } } - void _showPageSelector(BuildContext context, int totalPages, int currentPage) { + + void _showPageSelector(BuildContext context, int totalPages, int currentPage, bool switcherScreen) { showDialog( context: context, builder: (BuildContext context) { return QuranReadingPageSelector( + isPortrait: switcherScreen, currentPage: currentPage, scrollController: _gridScrollController, totalPages: totalPages, @@ -289,20 +377,13 @@ class _QuranReadingScreenState extends ConsumerState { FloatingActionButtonLocation _getFloatingActionButtonLocation(BuildContext context) { final TextDirection textDirection = Directionality.of(context); - final orientation = MediaQuery.of(context).orientation; - final isPortrait = orientation == Orientation.portrait; - - if (isPortrait) { - return FloatingActionButtonLocation.startFloat; - } else { - switch (textDirection) { - case TextDirection.ltr: - return FloatingActionButtonLocation.endFloat; - case TextDirection.rtl: - return FloatingActionButtonLocation.startFloat; - default: - return FloatingActionButtonLocation.endFloat; - } + switch (textDirection) { + case TextDirection.ltr: + return FloatingActionButtonLocation.endFloat; + case TextDirection.rtl: + return FloatingActionButtonLocation.startFloat; + default: + return FloatingActionButtonLocation.endFloat; } } @@ -325,21 +406,20 @@ class _QuranReadingScreenState extends ConsumerState { return KeyEventResult.ignored; } - KeyEventResult _handlePageScrollDownFocusGroupNode(FocusNode node, KeyEvent event) { + KeyEventResult _handlePageScrollDownFocusGroupNode(FocusNode node, KeyEvent event, bool isPortrait) { if (event is KeyDownEvent) { - final isPortrait = MediaQuery.of(context).orientation == Orientation.portrait; if (event.logicalKey == LogicalKeyboardKey.arrowDown && isPortrait) { - _scrollPageList(ScrollDirection.reverse); + _scrollPageList(ScrollDirection.reverse, isPortrait); return KeyEventResult.handled; } } return KeyEventResult.ignored; } - KeyEventResult _handlePageScrollUpFocusGroupNode(FocusNode node, KeyEvent event) { + KeyEventResult _handlePageScrollUpFocusGroupNode(FocusNode node, KeyEvent event, bool isPortrait) { if (event is KeyDownEvent) { if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - _scrollPageList(ScrollDirection.forward); + _scrollPageList(ScrollDirection.forward, isPortrait); return KeyEventResult.handled; } diff --git a/lib/src/pages/quran/widget/reading/moshaf_selector.dart b/lib/src/pages/quran/widget/reading/moshaf_selector.dart index 3a454d22e..8ed8f269e 100644 --- a/lib/src/pages/quran/widget/reading/moshaf_selector.dart +++ b/lib/src/pages/quran/widget/reading/moshaf_selector.dart @@ -14,10 +14,12 @@ import 'package:mawaqit/src/models/address_model.dart'; class MoshafSelector extends ConsumerWidget { final FocusNode focusNode; final bool isAutofocus; + final bool isPortrait; const MoshafSelector({ super.key, required this.focusNode, + this.isPortrait = true, this.isAutofocus = false, }); @@ -55,7 +57,7 @@ class MoshafSelector extends ConsumerWidget { }, borderRadius: BorderRadius.circular(20), child: Container( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: isPortrait ? 8 : 4), decoration: BoxDecoration( color: Colors.black.withOpacity(0.4), borderRadius: BorderRadius.circular(20), diff --git a/lib/src/pages/quran/widget/reading/quran_reading_page_selector.dart b/lib/src/pages/quran/widget/reading/quran_reading_page_selector.dart index 5d3c83379..9667f1f43 100644 --- a/lib/src/pages/quran/widget/reading/quran_reading_page_selector.dart +++ b/lib/src/pages/quran/widget/reading/quran_reading_page_selector.dart @@ -7,12 +7,14 @@ import 'package:sizer/sizer.dart'; class QuranReadingPageSelector extends ConsumerStatefulWidget { final int totalPages; final int currentPage; + final bool isPortrait; final ScrollController scrollController; const QuranReadingPageSelector({ required this.totalPages, required this.currentPage, required this.scrollController, + required this.isPortrait, }); @override @@ -47,55 +49,57 @@ class _QuranReadingPageSelectorState extends ConsumerState showPageSelector( - context, - quranReadingState.totalPages, - quranReadingState.currentPage, - ), + onTap: () => + showPageSelector(context, quranReadingState.totalPages, quranReadingState.currentPage, isPortrait), borderRadius: BorderRadius.circular(20), child: Container( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: isPortrait ? 8 : 4), decoration: BoxDecoration( color: Colors.black.withOpacity(0.4), borderRadius: BorderRadius.circular(20), @@ -190,8 +187,9 @@ Widget buildMoshafSelector( ) : Positioned( left: 10, - bottom: 1.h, + bottom: 0.5.h, child: MoshafSelector( + isPortrait: false, isAutofocus: !isThereCurrentDialogShowing, focusNode: focusNode, ), @@ -200,22 +198,36 @@ Widget buildMoshafSelector( Widget buildBackButton(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context, FocusNode focusNode) { return Positioned.directional( - start: 10, - textDirection: Directionality.of(context), - child: SwitchButton( - focusNode: focusNode, - opacity: 0.7, - iconSize: 14.sp, - splashFactorSize: 0.9, - icon: Icons.arrow_back_rounded, - onPressed: () { - if (isPortrait) { - userPrefs.orientationLandscape = true; - } - Navigator.pop(context); - }, - ), - ); + start: 10, + top: 10, + textDirection: Directionality.of(context), + child: Material( + color: Colors.transparent, + child: InkWell( + focusNode: focusNode, + onTap: () { + if (isPortrait) { + userPrefs.orientationLandscape = true; + } + Navigator.pop(context); + }, + borderRadius: BorderRadius.circular(40), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 16), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.4), + borderRadius: BorderRadius.circular(40), + ), + child: Center( + child: Icon( + Icons.arrow_back_rounded, + color: Colors.white, + size: 14.sp, + ), + ), + ), + ), + )); } Widget buildSvgPicture(SvgPicture svgPicture) { diff --git a/lib/src/services/mosque_manager.dart b/lib/src/services/mosque_manager.dart index 1821335c2..eac4896d8 100644 --- a/lib/src/services/mosque_manager.dart +++ b/lib/src/services/mosque_manager.dart @@ -81,7 +81,6 @@ class MosqueManager extends ChangeNotifier with WeatherMixin, AudioMixin, Mosque bool isToggleScreenActivated = false; int minuteBefore = 0; int minuteAfter = 0; - late SharedPreferences prefs; /// get current home url String buildUrl(String languageCode) { @@ -124,7 +123,6 @@ class MosqueManager extends ChangeNotifier with WeatherMixin, AudioMixin, Mosque isEventsSet = await ToggleScreenFeature.checkEventsScheduled(); minuteBefore = await getMinuteBefore(); minuteAfter = await getMinuteAfter(); - prefs = await SharedPreferences.getInstance(); notifyListeners(); } @@ -172,9 +170,9 @@ class MosqueManager extends ChangeNotifier with WeatherMixin, AudioMixin, Mosque _configSubscription?.cancel(); /// if getting item returns an error - onItemError(e, stack) { + onItemError(e, stack) async { logger.e(e, stackTrace: stack); - bool hasCachedMosque = prefs.getBool(MosqueManagerConstant.khasCachedMosque) ?? false; + bool hasCachedMosque = await sharedPref.read(MosqueManagerConstant.khasCachedMosque) ?? false; if (!hasCachedMosque) { mosque = null; notifyListeners(); @@ -201,7 +199,7 @@ class MosqueManager extends ChangeNotifier with WeatherMixin, AudioMixin, Mosque _mosqueSubscription = mosqueStream.listen( (e) async { mosque = e; - await prefs.setBool(MosqueManagerConstant.khasCachedMosque, true); + await sharedPref.save(MosqueManagerConstant.khasCachedMosque, true); _updateFlashEnabled(); notifyListeners(); }, diff --git a/lib/src/state_management/quran/recite/recite_notifier.dart b/lib/src/state_management/quran/recite/recite_notifier.dart index cdd0fb80e..7c41cb884 100644 --- a/lib/src/state_management/quran/recite/recite_notifier.dart +++ b/lib/src/state_management/quran/recite/recite_notifier.dart @@ -17,6 +17,8 @@ import 'package:mawaqit/src/domain/model/quran/moshaf_model.dart'; class ReciteNotifier extends AsyncNotifier { // Option> _cachedReciters = None(); Timer? _debounce; + String _currentQuery = ''; + bool _isAllReciters = true; @override build() async { @@ -38,9 +40,11 @@ class ReciteNotifier extends AsyncNotifier { } void setSearchQuery(String query, bool isAllReciters) { + _currentQuery = query.toLowerCase(); + _isAllReciters = isAllReciters; if (_debounce?.isActive ?? false) _debounce!.cancel(); _debounce = Timer(const Duration(milliseconds: 300), () { - _updateFilteredReciters(query.toLowerCase(), isAllReciters); + _updateFilteredReciters(_currentQuery, _isAllReciters); }); } @@ -48,13 +52,12 @@ class ReciteNotifier extends AsyncNotifier { final currentState = state.value; if (currentState == null) return; state = await AsyncValue.guard(() async { - if (isAllReciters) { - final filteredReciters = _filterReciters(currentState.reciters, query); - return currentState.copyWith(filteredReciters: filteredReciters); - } else { - final filteredFavoriteReciters = _filterReciters(currentState.favoriteReciters, query); - return currentState.copyWith(filteredFavoriteReciters: filteredFavoriteReciters); - } + final updatedFilteredReciters = _filterReciters(currentState.reciters, query); + final updatedFilteredFavorites = _filterReciters(currentState.favoriteReciters, query); + return currentState.copyWith( + filteredReciters: updatedFilteredReciters, + filteredFavoriteReciters: updatedFilteredFavorites, + ); }); } @@ -90,10 +93,14 @@ class ReciteNotifier extends AsyncNotifier { await _saveFavoriteReciters(reciter.id); final updatedFavorites = [...state.value!.favoriteReciters, reciter]; final updatedReciters = _sortReciters(state.value!.reciters, updatedFavorites); + final updatedFilteredFavorites = _filterReciters(updatedFavorites, _currentQuery); + final updatedFilteredReciters = _filterReciters(updatedReciters, _currentQuery); state = AsyncData( state.value!.copyWith( favoriteReciters: updatedFavorites, + filteredFavoriteReciters: updatedFilteredFavorites, reciters: updatedReciters, + filteredReciters: updatedFilteredReciters, ), ); } catch (e, s) { @@ -107,10 +114,14 @@ class ReciteNotifier extends AsyncNotifier { await reciteImpl.removeFavoriteReciter(reciter.id); final updatedFavorites = state.value!.favoriteReciters.where((r) => r.id != reciter.id).toList(); final updatedReciters = _sortReciters(state.value!.reciters, updatedFavorites); + final updatedFilteredFavorites = _filterReciters(updatedFavorites, _currentQuery); + final updatedFilteredReciters = _filterReciters(updatedReciters, _currentQuery); state = AsyncData( state.value!.copyWith( favoriteReciters: updatedFavorites, + filteredFavoriteReciters: updatedFilteredFavorites, reciters: updatedReciters, + filteredReciters: updatedFilteredReciters, ), ); } catch (e, s) { diff --git a/lib/src/state_management/quran/recite/recite_state.dart b/lib/src/state_management/quran/recite/recite_state.dart index f7aa2187d..25b87c8b0 100644 --- a/lib/src/state_management/quran/recite/recite_state.dart +++ b/lib/src/state_management/quran/recite/recite_state.dart @@ -47,8 +47,9 @@ class ReciteState extends Equatable { final reciterInfo = reciters.isNotEmpty ? reciters[0] : 'No reciters'; return 'ReciteState(reciters: $reciterInfo, selectedReciter: ${selectedReciter.hashCode}, ' 'selectedMoshaf: ${selectedMoshaf.hashCode},' - 'filteredReciters: ${filteredReciters.length}' - 'filteredFavoriteReciters: ${filteredFavoriteReciters.length})'; + 'filteredReciters: ${filteredReciters.length},' + 'filteredFavoriteReciters: ${filteredFavoriteReciters.length},' + 'favoriteReciters: ${favoriteReciters.length})'; } @override diff --git a/pubspec.yaml b/pubspec.yaml index b6a56ea1c..d9028a45a 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.16.0+1 +version: 1.16.1+1 environment: From 18498668f78c256d88fc8a0a4bc86ff33d25346c Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 24 Oct 2024 17:41:56 +0300 Subject: [PATCH 10/33] refactor: remove unused floating action buttons --- .../quran/reading/quran_reading_screen.dart | 78 ------------------- 1 file changed, 78 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 272895dff..2bb0ec5d6 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -5,7 +5,6 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mawaqit/i18n/l10n.dart'; -import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; import 'package:mawaqit/src/pages/quran/reading/widget/quran_floating_action_buttons.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_reading_widgets.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_surah_selector.dart'; @@ -13,8 +12,6 @@ import 'package:mawaqit/src/pages/quran/widget/reading/quran_surah_selector.dart import 'package:mawaqit/src/services/user_preferences_manager.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/quran/quran_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; @@ -27,8 +24,6 @@ import 'package:sizer/sizer.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/quran_reading_page_selector.dart'; -import '../../../data/data_source/device_info_data_source.dart'; - class QuranReadingScreen extends ConsumerStatefulWidget { const QuranReadingScreen({super.key}); @@ -48,7 +43,6 @@ class _QuranReadingScreenState extends ConsumerState { late FocusNode _portraitModeSwitchQuranFocusNode; late FocusNode _portraitModePageSelectorFocusNode; final ScrollController _gridScrollController = ScrollController(); - final GlobalKey _scaffoldKey = GlobalKey(); @override void initState() { @@ -281,78 +275,6 @@ class _QuranReadingScreenState extends ConsumerState { ); } - Widget buildFloatingPortrait(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { - return Padding( - padding: EdgeInsets.only(left: 30), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.max, - children: [ - _buildOrientationToggleButton(isPortrait), - _buildQuranModeButton(isPortrait, userPrefs, context), - ], - ), - ); - } - - Widget buildFloatingLandscape(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _buildOrientationToggleButton(isPortrait), - SizedBox(height: 10), - _buildQuranModeButton(isPortrait, userPrefs, context), - ], - ); - } - - Widget _buildOrientationToggleButton(bool isPortrait) { - return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: _switchScreenViewFocusNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - !isPortrait ? Icons.stay_current_portrait : Icons.stay_current_landscape, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: () => _toggleOrientation(), - heroTag: null, - ), - ); - } - - Widget _buildQuranModeButton(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context) { - return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: _switchQuranModeNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - Icons.headset, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: () async { - ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); - if (isPortrait) { - userPrefs.orientationLandscape = true; - } - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => ReciterSelectionScreen.withoutSurahName(), - ), - ); - }, - heroTag: null, - ), - ); - } - void _scrollPageList(ScrollDirection direction, isPortrait) { if (direction == ScrollDirection.forward) { ref.read(quranReadingNotifierProvider.notifier).previousPage(isPortrait: isPortrait); From 1ebab23c8f3e691be7059f96e100caf57edfd709 Mon Sep 17 00:00:00 2001 From: Yassin Date: Thu, 24 Oct 2024 19:26:26 +0300 Subject: [PATCH 11/33] refactor: migrate screen rotation to state management - Add isRotated field to QuranReadingState to manage rotation state - Add toggleRotation method to QuranReadingNotifier - Remove local ValueNotifier for rotation management - Update QuranFloatingActionControls to use state-managed rotation - Simplify _OrientationToggleButton to use state rotation - Remove orientation dependencies from UserPreferencesManager --- .../quran/reading/quran_reading_screen.dart | 60 +++--- .../widget/quran_floating_action_buttons.dart | 201 ++++++++++-------- .../quran/reading/quran_reading_notifer.dart | 8 + .../quran/reading/quran_reading_state.dart | 8 +- 4 files changed, 157 insertions(+), 120 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 2bb0ec5d6..a22dd2c99 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -68,12 +68,12 @@ class _QuranReadingScreenState extends ConsumerState { _switchToPlayQuranFocusNode = FocusNode(debugLabel: 'switch_to_play_quran_node'); } - bool _isRotated = false; + final ValueNotifier _isRotated = ValueNotifier(false); @override void dispose() { _disposeFocusNodes(); - + _isRotated.dispose(); super.dispose(); } @@ -89,12 +89,6 @@ class _QuranReadingScreenState extends ConsumerState { _switchToPlayQuranFocusNode.dispose(); } - void _toggleOrientation() { - setState(() { - _isRotated = !_isRotated; - }); - } - @override Widget build(BuildContext context) { final quranReadingState = ref.watch(quranReadingNotifierProvider); @@ -129,14 +123,15 @@ class _QuranReadingScreenState extends ConsumerState { _leftSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); _rightSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); _switchScreenViewFocusNode.onKeyEvent = - (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); + (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated.value); _portraitModePageSelectorFocusNode.onKeyEvent = - (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); - _switchQuranModeNode.onKeyEvent = (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); + (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated.value); + _switchQuranModeNode.onKeyEvent = + (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated.value); _portraitModeBackButtonFocusNode.onKeyEvent = - (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated); + (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated.value); _portraitModeSwitchQuranFocusNode.onKeyEvent = - (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated); + (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated.value); final autoReadingState = ref.watch(autoScrollNotifierProvider); @@ -145,22 +140,31 @@ class _QuranReadingScreenState extends ConsumerState { userPrefs.orientationLandscape = true; return true; }, - child: RotatedBox( - quarterTurns: _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, + child: ValueListenableBuilder( + valueListenable: _isRotated, + builder: (context, value, child) { + return quranReadingState.when( + data: (state) => 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), + ), + ), ), - body: _buildBody(quranReadingState, _isRotated, userPrefs, autoReadingState), - ), - ), + loading: () => const CircularProgressIndicator(), + error: (error, stack) => const Icon(Icons.error), + ); + }, )); } diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 8904c788e..43cc0006d 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -7,7 +7,6 @@ import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; -import 'package:provider/provider.dart'; import 'package:sizer/sizer.dart'; class QuranFloatingActionControls extends ConsumerWidget { @@ -24,51 +23,52 @@ class QuranFloatingActionControls extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final userPrefs = context.watch(); final quranReadingState = ref.watch(quranReadingNotifierProvider); - return OrientationBuilder( - builder: (context, orientation) { - final isPortrait = orientation == Orientation.portrait; - final autoScrollState = ref.watch(autoScrollNotifierProvider); - - if (autoScrollState.isAutoScrolling) { - return quranReadingState.maybeWhen( - orElse: () { - return CircularProgressIndicator(); - }, - data: (quranState) { - return _AutoScrollingReadingMode( - isPortrait: isPortrait, - quranReadingState: quranState, + final autoScrollState = ref.watch(autoScrollNotifierProvider); + + return Positioned( + right: 16, + bottom: 16, + child: quranReadingState.when( + data: (state) { + if (autoScrollState.isAutoScrolling) { + return _AutoScrollingReadingMode( + isPortrait: state.isRotated, + quranReadingState: state, + switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _PlayPauseButton( + isPortrait: state.isRotated, switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, - ); - }, - ); - } else { - return isPortrait - ? _FloatingPortrait( - userPrefs: userPrefs, - isPortrait: isPortrait, - switchScreenViewFocusNode: switchScreenViewFocusNode, - switchQuranModeNode: switchQuranModeNode, - switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, - ) - : _FloatingLandscape( - userPrefs: userPrefs, - isPortrait: isPortrait, - switchScreenViewFocusNode: switchScreenViewFocusNode, - switchQuranModeNode: switchQuranModeNode, - switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, + ), + SizedBox(height: 12), + _OrientationToggleButton( + switchScreenViewFocusNode: switchScreenViewFocusNode, + ), + SizedBox(height: 12), + _QuranModeButton( + isPortrait: state.isRotated, + switchQuranModeNode: switchQuranModeNode, + ), + ], ); - } - }, + }, + loading: () => const CircularProgressIndicator(), + error: (_, __) => const Icon(Icons.error), + ), ); } } + class _FloatingPortrait extends StatelessWidget { final UserPreferencesManager userPrefs; - final bool isPortrait; + final ValueNotifier isPortrait; final FocusNode switchScreenViewFocusNode; final FocusNode switchQuranModeNode; final FocusNode switchToPlayQuranFocusNode; @@ -87,19 +87,16 @@ class _FloatingPortrait extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ _OrientationToggleButton( - userPrefs: userPrefs, - isPortrait: isPortrait, switchScreenViewFocusNode: switchScreenViewFocusNode, ), SizedBox(width: 200.sp), _QuranModeButton( - userPrefs: userPrefs, - isPortrait: isPortrait, + isPortrait: isPortrait.value, switchQuranModeNode: switchQuranModeNode, ), SizedBox(width: 200.sp), _PlayPauseButton( - isPortrait: isPortrait, + isPortrait: isPortrait.value, switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, ), ], @@ -109,7 +106,7 @@ class _FloatingPortrait extends StatelessWidget { class _FloatingLandscape extends StatelessWidget { final UserPreferencesManager userPrefs; - final bool isPortrait; + final ValueNotifier isPortrait; final FocusNode switchScreenViewFocusNode; final FocusNode switchQuranModeNode; final FocusNode switchToPlayQuranFocusNode; @@ -128,19 +125,16 @@ class _FloatingLandscape extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.end, children: [ _PlayPauseButton( - isPortrait: isPortrait, + isPortrait: isPortrait.value, switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, ), SizedBox(height: 1.h), _OrientationToggleButton( - userPrefs: userPrefs, - isPortrait: isPortrait, switchScreenViewFocusNode: switchScreenViewFocusNode, ), SizedBox(height: 1.h), _QuranModeButton( - userPrefs: userPrefs, - isPortrait: isPortrait, + isPortrait: isPortrait.value, switchQuranModeNode: switchQuranModeNode, ), ], @@ -148,51 +142,44 @@ class _FloatingLandscape extends StatelessWidget { } } -class _OrientationToggleButton extends StatelessWidget { - final UserPreferencesManager userPrefs; - final bool isPortrait; - final FocusNode switchScreenViewFocusNode; - - const _OrientationToggleButton({ - required this.userPrefs, - required this.isPortrait, - required this.switchScreenViewFocusNode, - }); - +class _OrientationToggleButtonState extends State<_OrientationToggleButton> { @override Widget build(BuildContext context) { - return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: switchScreenViewFocusNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - !isPortrait ? Icons.stay_current_portrait : Icons.stay_current_landscape, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: () => _toggleOrientation(context), - heroTag: null, - ), + return Consumer( + builder: (context, ref, child) { + final quranReadingState = ref.watch(quranReadingNotifierProvider); + return quranReadingState.when( + data: (state) => SizedBox( + width: state.isRotated ? 35.sp : 30.sp, + height: state.isRotated ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: widget.switchScreenViewFocusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + !state.isRotated ? Icons.stay_current_portrait : Icons.stay_current_landscape, + color: Colors.white, + size: state.isRotated ? 20.sp : 15.sp, + ), + onPressed: () { + ref.read(quranReadingNotifierProvider.notifier).toggleRotation(); + }, + heroTag: null, + ), + ), + loading: () => const CircularProgressIndicator(), + error: (error, stack) => const Icon(Icons.error), + ); + }, ); } - - void _toggleOrientation(BuildContext context) { - final newOrientation = - MediaQuery.of(context).orientation == Orientation.portrait ? Orientation.landscape : Orientation.portrait; - - userPrefs.orientationLandscape = newOrientation == Orientation.landscape; - } } + class _QuranModeButton extends ConsumerWidget { - final UserPreferencesManager userPrefs; final bool isPortrait; final FocusNode switchQuranModeNode; const _QuranModeButton({ - required this.userPrefs, required this.isPortrait, required this.switchQuranModeNode, }); @@ -212,9 +199,6 @@ class _QuranModeButton extends ConsumerWidget { ), onPressed: () async { ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); - if (isPortrait) { - userPrefs.orientationLandscape = true; - } Navigator.pushReplacement( context, MaterialPageRoute( @@ -384,9 +368,9 @@ class _SpeedControls extends ConsumerWidget { onPressed: () { final pageHeight = MediaQuery.of(context).size.height; ref.read(autoScrollNotifierProvider.notifier).increaseSpeed( - quranReadingState.currentPage, - pageHeight, - ); + quranReadingState.currentPage, + pageHeight, + ); }, heroTag: 'increase_speed', ), @@ -402,9 +386,9 @@ class _SpeedControls extends ConsumerWidget { onPressed: () { final pageHeight = MediaQuery.of(context).size.height; ref.read(autoScrollNotifierProvider.notifier).decreaseSpeed( - quranReadingState.currentPage, - pageHeight, - ); + quranReadingState.currentPage, + pageHeight, + ); }, heroTag: 'decrease_speed', ), @@ -413,3 +397,38 @@ class _SpeedControls extends ConsumerWidget { ); } } + +class _OrientationToggleButton extends ConsumerWidget { + final FocusNode switchScreenViewFocusNode; + + const _OrientationToggleButton({ + required this.switchScreenViewFocusNode, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final quranReadingState = ref.watch(quranReadingNotifierProvider); + + return quranReadingState.when( + data: (state) => SizedBox( + width: state.isRotated ? 35.sp : 30.sp, + height: state.isRotated ? 35.sp : 30.sp, + child: FloatingActionButton( + focusNode: switchScreenViewFocusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + !state.isRotated ? Icons.stay_current_portrait : Icons.stay_current_landscape, + color: Colors.white, + size: state.isRotated ? 20.sp : 15.sp, + ), + onPressed: () { + ref.read(quranReadingNotifierProvider.notifier).toggleRotation(); + }, + heroTag: null, + ), + ), + loading: () => const CircularProgressIndicator(), + error: (_, __) => const Icon(Icons.error), + ); + } +} 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 6c4cfaa5e..d6fd02e25 100644 --- a/lib/src/state_management/quran/reading/quran_reading_notifer.dart +++ b/lib/src/state_management/quran/reading/quran_reading_notifer.dart @@ -165,6 +165,14 @@ class QuranReadingNotifier extends AutoDisposeAsyncNotifier { return ""; } + Future toggleRotation() async { + state = await AsyncValue.guard(() async { + return state.value!.copyWith( + isRotated: !state.value!.isRotated, + ); + }); + } + Future> _loadSvgs({required MoshafType moshafType}) async { final repository = await ref.read(quranReadingRepositoryProvider.future); return repository.loadAllSvgs(moshafType); diff --git a/lib/src/state_management/quran/reading/quran_reading_state.dart b/lib/src/state_management/quran/reading/quran_reading_state.dart index e7f795038..cb797dacf 100644 --- a/lib/src/state_management/quran/reading/quran_reading_state.dart +++ b/lib/src/state_management/quran/reading/quran_reading_state.dart @@ -11,6 +11,7 @@ class QuranReadingState extends Equatable { final PageController pageController; final List suwar; final String currentSurahName; + final bool isRotated; QuranReadingState({ required this.currentJuz, @@ -20,6 +21,7 @@ class QuranReadingState extends Equatable { required this.pageController, required this.suwar, required this.currentSurahName, + this.isRotated = false, }); QuranReadingState copyWith({ @@ -31,6 +33,7 @@ class QuranReadingState extends Equatable { PageController? pageController, List? suwar, String? currentSurahName, + bool? isRotated, }) { return QuranReadingState( currentJuz: currentJuz ?? this.currentJuz, @@ -40,6 +43,7 @@ class QuranReadingState extends Equatable { pageController: pageController ?? this.pageController, suwar: suwar ?? this.suwar, currentSurahName: currentSurahName ?? this.currentSurahName, + isRotated: isRotated ?? this.isRotated, ); } @@ -52,6 +56,7 @@ class QuranReadingState extends Equatable { pageController, suwar, currentSurahName, + isRotated, ]; int get totalPages => svgs.length; @@ -59,6 +64,7 @@ class QuranReadingState extends Equatable { @override String toString() { return 'QuranReadingState{currentJuz: $currentJuz, currentSurah: $currentSurah, currentPage: $currentPage, ' - 'svgs: ${svgs.length}, totalPages: $totalPages}'; + 'svgs: ${svgs.length}, totalPages: $totalPages, pageController: $pageController, suwar: ${suwar.length}, ' + 'isRotated: $isRotated}'; } } From 278805b0ef0cf8e038c8c330de91e741bdb2c580 Mon Sep 17 00:00:00 2001 From: Yassin Date: Fri, 25 Oct 2024 23:20:13 +0300 Subject: [PATCH 12/33] refactor(quran): improve keyboard navigation and focus management - Replace custom key event handlers with FocusTraversalPolicy for better focus management - Add ArrowButtonsFocusTraversalPolicy to handle navigation between left/right buttons - Implement up/down navigation from arrow buttons to back button and page selector - Fix positioning issues with Stack and Positioned widgets - Remove ValueNotifier in favor of setState for rotation state management - Clean up widget hierarchy and remove redundant wrapper classes - Add proper focus order using FocusTraversalOrder - Fix duplicate Positioned widgets causing layout issues - Improve code organization and readability --- .../quran/reading/quran_reading_screen.dart | 322 +++++++++++------ .../widget/quran_floating_action_buttons.dart | 338 +++++++++++------- .../widget/reading/quran_reading_widgets.dart | 111 ++++-- 3 files changed, 520 insertions(+), 251 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index a22dd2c99..fe3ce261b 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; @@ -43,6 +44,7 @@ class _QuranReadingScreenState extends ConsumerState { late FocusNode _portraitModeSwitchQuranFocusNode; late FocusNode _portraitModePageSelectorFocusNode; final ScrollController _gridScrollController = ScrollController(); + bool _isRotated = false; @override void initState() { @@ -68,12 +70,9 @@ class _QuranReadingScreenState extends ConsumerState { _switchToPlayQuranFocusNode = FocusNode(debugLabel: 'switch_to_play_quran_node'); } - final ValueNotifier _isRotated = ValueNotifier(false); - @override void dispose() { _disposeFocusNodes(); - _isRotated.dispose(); super.dispose(); } @@ -120,60 +119,63 @@ class _QuranReadingScreenState extends ConsumerState { } }); - _leftSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); - _rightSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); - _switchScreenViewFocusNode.onKeyEvent = - (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated.value); - _portraitModePageSelectorFocusNode.onKeyEvent = - (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated.value); - _switchQuranModeNode.onKeyEvent = - (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated.value); - _portraitModeBackButtonFocusNode.onKeyEvent = - (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated.value); - _portraitModeSwitchQuranFocusNode.onKeyEvent = - (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated.value); + // _leftSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); + // _rightSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); + // _switchScreenViewFocusNode.onKeyEvent = + // (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); + // _portraitModePageSelectorFocusNode.onKeyEvent = + // (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); + // _switchQuranModeNode.onKeyEvent = (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); + // _portraitModeBackButtonFocusNode.onKeyEvent = + // (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated); + // _portraitModeSwitchQuranFocusNode.onKeyEvent = + // (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated); final autoReadingState = ref.watch(autoScrollNotifierProvider); return WillPopScope( - onWillPop: () async { - userPrefs.orientationLandscape = true; - return true; - }, - child: ValueListenableBuilder( - valueListenable: _isRotated, - builder: (context, value, child) { - return quranReadingState.when( - data: (state) => 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), + onWillPop: () async { + userPrefs.orientationLandscape = true; + return true; + }, + child: quranReadingState.when( + data: (state) { + setState(() { + _isRotated = state.isRotated; + }); + return RotatedBox( + quarterTurns: state.isRotated ? -1 : 0, + child: FocusTraversalGroup( + policy: OrderedTraversalPolicy(), + 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: () => const CircularProgressIndicator(), - error: (error, stack) => const Icon(Icons.error), - ); - }, - )); + ), + ); + }, + loading: () => const CircularProgressIndicator(), + error: (error, stack) => const Icon(Icons.error), + ), + ); } Widget _buildBody( - AsyncValue quranReadingState, - bool isPortrait, - UserPreferencesManager userPrefs, - AutoScrollState autoScrollState, - ) { + AsyncValue quranReadingState, + bool isPortrait, + UserPreferencesManager userPrefs, + AutoScrollState autoScrollState, + ) { final color = Theme.of(context).primaryColor; return quranReadingState.when( loading: () => Center( @@ -188,33 +190,106 @@ class _QuranReadingScreenState extends ConsumerState { data: (quranReadingState) { return Stack( children: [ + // Main content (Quran pages) autoScrollState.isSinglePageView ? buildAutoScrollView(quranReadingState, ref, autoScrollState) : isPortrait - ? buildVerticalPageView(quranReadingState, ref) - : buildHorizontalPageView(quranReadingState, ref, context), + ? buildVerticalPageView(quranReadingState, ref) + : buildHorizontalPageView(quranReadingState, ref, context), + if (!isPortrait) ...[ - buildRightSwitchButton( - context, - _rightSkipButtonFocusNode, - () => _scrollPageList(ScrollDirection.forward, isPortrait), + // Back button + Positioned( + top: 10, + left: 10, + child: FocusTraversalOrder( + order: NumericFocusOrder(1), + child: buildBackButton( + isPortrait, + userPrefs, + context, + _backButtonFocusNode, + ), + ), + ), + + // Navigation buttons + FocusTraversalGroup( + policy: ArrowButtonsFocusTraversalPolicy( + backButtonNode: _backButtonFocusNode, + pageSelectorNode: _portraitModePageSelectorFocusNode, + ), + child: Stack( + children: [ + // Left button + Positioned( + left: 10, + top: 0, + bottom: 0, + child: Center( + child: FocusTraversalOrder( + order: NumericFocusOrder(2), + child: buildLeftSwitchButton( + context, + _leftSkipButtonFocusNode, + () => _scrollPageList(ScrollDirection.reverse, isPortrait), + ), + ), + ), + ), + + // Right button + Positioned( + right: 10, + top: 0, + bottom: 0, + child: Center( + child: FocusTraversalOrder( + order: NumericFocusOrder(3), + child: buildRightSwitchButton( + context, + _rightSkipButtonFocusNode, + () => _scrollPageList(ScrollDirection.forward, isPortrait), + ), + ), + ), + ), + ], + ), ), - buildLeftSwitchButton( - context, - _leftSkipButtonFocusNode, - () => _scrollPageList(ScrollDirection.reverse, isPortrait), + + // Page selector + Positioned( + top: 60, + left: 0, + right: 0, + child: FocusTraversalOrder( + order: NumericFocusOrder(4), + child: buildPageNumberIndicator( + quranReadingState, + isPortrait, + context, + _portraitModePageSelectorFocusNode, + _showPageSelector, + ), + ), + ), + + // Moshaf selector + Positioned( + top: 10, + right: 60, + child: buildMoshafSelector( + isPortrait, + context, + _switchQuranFocusNode, + _isThereCurrentDialogShowing(context), + ), ), + + // Surah title + buildShowSurah(quranReadingState), ], - buildPageNumberIndicator( - quranReadingState, isPortrait, context, _portraitModePageSelectorFocusNode, _showPageSelector), - buildMoshafSelector( - isPortrait, - context, - isPortrait ? _portraitModeSwitchQuranFocusNode : _switchQuranFocusNode, - _isThereCurrentDialogShowing(context)), - buildBackButton( - isPortrait, userPrefs, context, isPortrait ? _portraitModeBackButtonFocusNode : _backButtonFocusNode), - isPortrait ? SizedBox() : buildShowSurah(quranReadingState), ], ); }, @@ -313,45 +388,92 @@ class _QuranReadingScreenState extends ConsumerState { } } - KeyEventResult _handleSwitcherFocusGroupNode(FocusNode node, KeyEvent event) { - if (event is KeyDownEvent) { - if (event.logicalKey == LogicalKeyboardKey.arrowRight) { - _rightSkipButtonFocusNode.requestFocus(); - return KeyEventResult.handled; - } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { - _leftSkipButtonFocusNode.requestFocus(); - return KeyEventResult.handled; - } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - _backButtonFocusNode.requestFocus(); - return KeyEventResult.handled; - } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { - _switchQuranFocusNode.requestFocus(); - return KeyEventResult.handled; - } - } - return KeyEventResult.ignored; + bool _isThereCurrentDialogShowing(BuildContext context) => ModalRoute.of(context)?.isCurrent != true; +} + + +class ArrowButtonsFocusTraversalPolicy extends FocusTraversalPolicy { + final FocusNode backButtonNode; + final FocusNode pageSelectorNode; + + const ArrowButtonsFocusTraversalPolicy({ + super.requestFocusCallback, + required this.backButtonNode, + required this.pageSelectorNode, + }); + + @override + FocusNode? findFirstFocus(FocusNode currentNode, {bool ignoreCurrentFocus = false}) { + final nodes = currentNode.nearestScope!.traversalDescendants; + return nodes.firstWhereOrNull((node) => node.debugLabel?.contains('left_skip_node') == true); } - KeyEventResult _handlePageScrollDownFocusGroupNode(FocusNode node, KeyEvent event, bool isPortrait) { - if (event is KeyDownEvent) { - if (event.logicalKey == LogicalKeyboardKey.arrowDown && isPortrait) { - _scrollPageList(ScrollDirection.reverse, isPortrait); - return KeyEventResult.handled; - } + @override + FocusNode findLastFocus(FocusNode currentNode, {bool ignoreCurrentFocus = false}) { + final nodes = currentNode.nearestScope!.traversalDescendants; + return nodes.firstWhereOrNull((node) => node.debugLabel?.contains('right_skip_node') == true) ?? currentNode; + } + + @override + FocusNode? findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction) { + switch (direction) { + case TraversalDirection.up: + return backButtonNode; + case TraversalDirection.down: + return pageSelectorNode; + case TraversalDirection.left: + case TraversalDirection.right: + return null; } - return KeyEventResult.ignored; } - KeyEventResult _handlePageScrollUpFocusGroupNode(FocusNode node, KeyEvent event, bool isPortrait) { - if (event is KeyDownEvent) { - if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - _scrollPageList(ScrollDirection.forward, isPortrait); + @override + Iterable sortDescendants(Iterable descendants, FocusNode currentNode) { + final arrowNodes = descendants.where((node) => + node.debugLabel?.contains('left_skip_node') == true || + node.debugLabel?.contains('right_skip_node') == true + ).toList(); + + mergeSort(arrowNodes, compare: (a, b) { + final aIsLeft = a.debugLabel?.contains('left_skip_node') == true; + final bIsLeft = b.debugLabel?.contains('left_skip_node') == true; + return aIsLeft ? -1 : 1; + }); - return KeyEventResult.handled; - } - } - return KeyEventResult.ignored; + return arrowNodes; } - bool _isThereCurrentDialogShowing(BuildContext context) => ModalRoute.of(context)?.isCurrent != true; + @override + bool inDirection(FocusNode currentNode, TraversalDirection direction) { + final nodes = currentNode.nearestScope!.traversalDescendants; + final leftNode = nodes.firstWhereOrNull((node) => node.debugLabel?.contains('left_skip_node') == true); + final rightNode = nodes.firstWhereOrNull((node) => node.debugLabel?.contains('right_skip_node') == true); + + switch (direction) { + case TraversalDirection.left: + if (currentNode == rightNode && leftNode != null) { + requestFocusCallback(leftNode); + return true; + } + return false; + case TraversalDirection.right: + if (currentNode == leftNode && rightNode != null) { + requestFocusCallback(rightNode); + return true; + } + return false; + case TraversalDirection.up: + if ((currentNode == leftNode || currentNode == rightNode) && backButtonNode.canRequestFocus) { + requestFocusCallback(backButtonNode); + return true; + } + return false; + case TraversalDirection.down: + if ((currentNode == leftNode || currentNode == rightNode) && pageSelectorNode.canRequestFocus) { + requestFocusCallback(pageSelectorNode); + return true; + } + return false; + } + } } diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 43cc0006d..ea615df75 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -1,7 +1,8 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mawaqit/src/pages/quran/page/reciter_selection_screen.dart'; -import 'package:mawaqit/src/services/user_preferences_manager.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/quran/quran_state.dart'; import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart'; @@ -9,7 +10,7 @@ import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; import 'package:sizer/sizer.dart'; -class QuranFloatingActionControls extends ConsumerWidget { +class QuranFloatingActionControls extends ConsumerStatefulWidget { final FocusNode switchScreenViewFocusNode; final FocusNode switchQuranModeNode; final FocusNode switchToPlayQuranFocusNode; @@ -22,158 +23,115 @@ class QuranFloatingActionControls extends ConsumerWidget { }); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => _QuranFloatingActionControlsState(); +} + +class _QuranFloatingActionControlsState extends ConsumerState { + late FocusScopeNode focusScopeNode; + + @override + void initState() { + focusScopeNode = FocusScopeNode(debugLabel: 'quran_floating_action_controls'); + super.initState(); + } + + @override + void dispose() { + focusScopeNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { final quranReadingState = ref.watch(quranReadingNotifierProvider); final autoScrollState = ref.watch(autoScrollNotifierProvider); - return Positioned( - right: 16, - bottom: 16, - child: quranReadingState.when( - data: (state) { - if (autoScrollState.isAutoScrolling) { - return _AutoScrollingReadingMode( - isPortrait: state.isRotated, - quranReadingState: state, - switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, - ); - } + return quranReadingState.when( + data: (state) { + if (autoScrollState.isAutoScrolling) { + return _AutoScrollingReadingMode( + isPortrait: state.isRotated, + quranReadingState: state, + switchToPlayQuranFocusNode: widget.switchToPlayQuranFocusNode, + ); + } - return Column( + return FocusTraversalGroup( + policy: WidgetOrderTraversalPolicy(), + child: Column( mainAxisSize: MainAxisSize.min, children: [ _PlayPauseButton( isPortrait: state.isRotated, - switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, + switchToPlayQuranFocusNode: widget.switchToPlayQuranFocusNode, ), SizedBox(height: 12), _OrientationToggleButton( - switchScreenViewFocusNode: switchScreenViewFocusNode, + switchScreenViewFocusNode: widget.switchScreenViewFocusNode, ), SizedBox(height: 12), _QuranModeButton( isPortrait: state.isRotated, - switchQuranModeNode: switchQuranModeNode, + switchQuranModeNode: widget.switchQuranModeNode, ), ], - ); - }, - loading: () => const CircularProgressIndicator(), - error: (_, __) => const Icon(Icons.error), - ), - ); - } -} - - -class _FloatingPortrait extends StatelessWidget { - final UserPreferencesManager userPrefs; - final ValueNotifier isPortrait; - final FocusNode switchScreenViewFocusNode; - final FocusNode switchQuranModeNode; - final FocusNode switchToPlayQuranFocusNode; - - const _FloatingPortrait({ - required this.userPrefs, - required this.isPortrait, - required this.switchScreenViewFocusNode, - required this.switchQuranModeNode, - required this.switchToPlayQuranFocusNode, - }); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - _OrientationToggleButton( - switchScreenViewFocusNode: switchScreenViewFocusNode, - ), - SizedBox(width: 200.sp), - _QuranModeButton( - isPortrait: isPortrait.value, - switchQuranModeNode: switchQuranModeNode, - ), - SizedBox(width: 200.sp), - _PlayPauseButton( - isPortrait: isPortrait.value, - switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, - ), - ], - ); - } -} - -class _FloatingLandscape extends StatelessWidget { - final UserPreferencesManager userPrefs; - final ValueNotifier isPortrait; - final FocusNode switchScreenViewFocusNode; - final FocusNode switchQuranModeNode; - final FocusNode switchToPlayQuranFocusNode; - - const _FloatingLandscape({ - required this.userPrefs, - required this.isPortrait, - required this.switchScreenViewFocusNode, - required this.switchQuranModeNode, - required this.switchToPlayQuranFocusNode, - }); - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _PlayPauseButton( - isPortrait: isPortrait.value, - switchToPlayQuranFocusNode: switchToPlayQuranFocusNode, - ), - SizedBox(height: 1.h), - _OrientationToggleButton( - switchScreenViewFocusNode: switchScreenViewFocusNode, - ), - SizedBox(height: 1.h), - _QuranModeButton( - isPortrait: isPortrait.value, - switchQuranModeNode: switchQuranModeNode, - ), - ], - ); - } -} - -class _OrientationToggleButtonState extends State<_OrientationToggleButton> { - @override - Widget build(BuildContext context) { - return Consumer( - builder: (context, ref, child) { - final quranReadingState = ref.watch(quranReadingNotifierProvider); - return quranReadingState.when( - data: (state) => SizedBox( - width: state.isRotated ? 35.sp : 30.sp, - height: state.isRotated ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: widget.switchScreenViewFocusNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - !state.isRotated ? Icons.stay_current_portrait : Icons.stay_current_landscape, - color: Colors.white, - size: state.isRotated ? 20.sp : 15.sp, - ), - onPressed: () { - ref.read(quranReadingNotifierProvider.notifier).toggleRotation(); - }, - heroTag: null, - ), ), - loading: () => const CircularProgressIndicator(), - error: (error, stack) => const Icon(Icons.error), ); }, + loading: () => const CircularProgressIndicator(), + error: (_, __) => const Icon(Icons.error), ); } -} + KeyEventResult _handleFloatingActionButtons(FocusNode node, event, bool isRotated) { + if (event is KeyDownEvent) { + if (isRotated) { + // Landscape mode navigation + switch (event.logicalKey) { + case LogicalKeyboardKey.arrowUp: + // Navigate between floating action buttons vertically + FocusScope.of(context).previousFocus(); + return KeyEventResult.handled; + + case LogicalKeyboardKey.arrowDown: + // Navigate between floating action buttons vertically + FocusScope.of(context).nextFocus(); + return KeyEventResult.handled; + + case LogicalKeyboardKey.arrowLeft: + // Navigate to left side buttons (Quran switcher, etc.) + if (node == widget.switchToPlayQuranFocusNode) { + FocusScope.of(context).requestFocus(widget.switchQuranModeNode); + } + return KeyEventResult.handled; + + case LogicalKeyboardKey.arrowRight: + // Navigate to right side floating action buttons + if (node == widget.switchQuranModeNode) { + FocusScope.of(context).requestFocus(widget.switchToPlayQuranFocusNode); + } + return KeyEventResult.handled; + } + } else { + if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { + node.focusInDirection(TraversalDirection.left); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) { + ref.read(quranReadingNotifierProvider.notifier).nextPage(); + node.previousFocus(); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + FocusScope.of(context).previousFocus(); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + FocusScope.of(context).nextFocus(); + return KeyEventResult.handled; + } + } + } + return KeyEventResult.ignored; + } +} class _QuranModeButton extends ConsumerWidget { final bool isPortrait; @@ -432,3 +390,121 @@ class _OrientationToggleButton extends ConsumerWidget { ); } } + + +class QuranFocusTraversalPolicy extends ReadingOrderTraversalPolicy { + @override + bool inDirection(FocusNode currentNode, TraversalDirection direction) { + final FocusScopeNode scope = currentNode.nearestScope!; + final List nodes = scope.traversalDescendants.toList(); + + // Get specific nodes by their debug labels with null safety + final leftArrowNode = nodes.firstWhereOrNull( + (node) => node.debugLabel?.contains('left_skip_node') == true, + ); + final rightArrowNode = nodes.firstWhereOrNull( + (node) => node.debugLabel?.contains('right_skip_node') == true, + ); + final playButtonNode = nodes.firstWhereOrNull( + (node) => node.debugLabel?.contains('switch_to_play_quran_node') == true, + ); + final orientationButtonNode = nodes.firstWhereOrNull( + (node) => node.debugLabel?.contains('switch_screen_view_node') == true, + ); + final modeButtonNode = nodes.firstWhereOrNull( + (node) => node.debugLabel?.contains('switch_quran_mode_node') == true, + ); + final hafsFocusNode = nodes.firstWhereOrNull( + (node) => node.debugLabel?.contains('go_to_hafs_node') == true, + ); + final pageSelectorNode = nodes.firstWhereOrNull( + (node) => node.debugLabel?.contains('page_selector_node') == true, + ); + final backButtonNode = nodes.firstWhereOrNull( + (node) => node.debugLabel?.contains('back_button_node') == true, + ); + + // Handle navigation between arrow buttons and related controls + if (currentNode == leftArrowNode || currentNode == rightArrowNode || + currentNode == hafsFocusNode || currentNode == pageSelectorNode || + currentNode == backButtonNode) { + switch (direction) { + case TraversalDirection.left: + if (currentNode == rightArrowNode && leftArrowNode != null) { + requestFocusCallback(leftArrowNode); + return true; + } else if (currentNode == hafsFocusNode && pageSelectorNode != null) { + requestFocusCallback(pageSelectorNode); + return true; + } + return false; + + case TraversalDirection.right: + if (currentNode == leftArrowNode && rightArrowNode != null) { + requestFocusCallback(rightArrowNode); + return true; + } else if (currentNode == pageSelectorNode && hafsFocusNode != null) { + requestFocusCallback(hafsFocusNode); + return true; + } + return false; + + case TraversalDirection.down: + if (currentNode == rightArrowNode && playButtonNode != null) { + requestFocusCallback(playButtonNode); + return true; + } else if (currentNode == leftArrowNode && hafsFocusNode != null) { + requestFocusCallback(hafsFocusNode); + return true; + } + return false; + + case TraversalDirection.up: + if (currentNode == leftArrowNode && backButtonNode != null) { + requestFocusCallback(backButtonNode); + return true; + } + return false; + } + } + + // Handle navigation in FAB column + if (currentNode == playButtonNode || + currentNode == orientationButtonNode || + currentNode == modeButtonNode) { + switch (direction) { + case TraversalDirection.up: + if (currentNode == orientationButtonNode && playButtonNode != null) { + requestFocusCallback(playButtonNode); + return true; + } else if (currentNode == modeButtonNode && orientationButtonNode != null) { + requestFocusCallback(orientationButtonNode); + return true; + } + return false; + + case TraversalDirection.down: + if (currentNode == playButtonNode && orientationButtonNode != null) { + requestFocusCallback(orientationButtonNode); + return true; + } else if (currentNode == orientationButtonNode && modeButtonNode != null) { + requestFocusCallback(modeButtonNode); + return true; + } + return false; + + case TraversalDirection.left: + if (currentNode == playButtonNode && rightArrowNode != null) { + requestFocusCallback(rightArrowNode); + return true; + } + return false; + + case TraversalDirection.right: + return false; + } + } + + return super.inDirection(currentNode, direction); + } +} diff --git a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart index 3ef138747..3f467fd6e 100644 --- a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart +++ b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -100,31 +101,37 @@ Widget buildHorizontalPageView(QuranReadingState quranReadingState, WidgetRef re } Widget buildRightSwitchButton(BuildContext context, FocusNode focusNode, Function() onPressed) { - return Positioned( - right: 10, - top: 0, - bottom: 0, - child: SwitchButton( - focusNode: focusNode, - opacity: 0.7, - iconSize: 14.sp, - icon: Directionality.of(context) == TextDirection.ltr ? Icons.arrow_forward_ios : Icons.arrow_back_ios, - onPressed: onPressed, + return FocusTraversalOrder( + order: NumericFocusOrder(2), + child: Positioned( + right: 10, + top: 0, + bottom: 0, + child: SwitchButton( + focusNode: focusNode, + opacity: 0.7, + iconSize: 14.sp, + icon: Directionality.of(context) == TextDirection.ltr ? Icons.arrow_forward_ios : Icons.arrow_back_ios, + onPressed: onPressed, + ), ), ); } Widget buildLeftSwitchButton(BuildContext context, FocusNode focusNode, Function() onPressed) { - return Positioned( - left: 10, - top: 0, - bottom: 0, - child: SwitchButton( - focusNode: focusNode, - opacity: 0.7, - iconSize: 14.sp, - icon: Directionality.of(context) != TextDirection.ltr ? Icons.arrow_forward_ios : Icons.arrow_back_ios, - onPressed: onPressed, + return FocusTraversalOrder( + order: NumericFocusOrder(1), + child: Positioned( + left: 10, + top: 0, + bottom: 0, + child: SwitchButton( + focusNode: focusNode, + opacity: 0.7, + iconSize: 14.sp, + icon: Directionality.of(context) != TextDirection.ltr ? Icons.arrow_forward_ios : Icons.arrow_back_ios, + onPressed: onPressed, + ), ), ); } @@ -243,3 +250,67 @@ Widget buildSvgPicture(SvgPicture svgPicture) { ), ); } + + +class ArrowButtonsFocusTraversalPolicy extends FocusTraversalPolicy { + const ArrowButtonsFocusTraversalPolicy({super.requestFocusCallback}); + + @override + FocusNode? findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction) { + final nodes = currentNode.nearestScope!.traversalDescendants; + return nodes.firstWhereOrNull((node) => node.debugLabel?.contains('left_skip_node') == true); + } + + @override + Iterable sortDescendants(Iterable descendants, FocusNode currentNode) { + // Filter only the arrow button nodes + final arrowNodes = descendants.where((node) => + node.debugLabel?.contains('left_skip_node') == true || + node.debugLabel?.contains('right_skip_node') == true + ).toList(); + + // Sort them so left is first, right is second + mergeSort(arrowNodes, compare: (a, b) { + final aIsLeft = a.debugLabel?.contains('left_skip_node') == true; + final bIsLeft = b.debugLabel?.contains('left_skip_node') == true; + return aIsLeft ? -1 : 1; + }); + + return arrowNodes; + } + + @override + bool inDirection(FocusNode currentNode, TraversalDirection direction) { + final scope = currentNode.nearestScope!; + final descendants = scope.traversalDescendants; + + final leftNode = descendants.firstWhereOrNull( + (node) => node.debugLabel?.contains('left_skip_node') == true + ); + final rightNode = descendants.firstWhereOrNull( + (node) => node.debugLabel?.contains('right_skip_node') == true + ); + + if (leftNode == null || rightNode == null) return false; + + switch (direction) { + case TraversalDirection.left: + if (currentNode == rightNode) { + requestFocusCallback(leftNode); + return true; + } + return false; + + case TraversalDirection.right: + if (currentNode == leftNode) { + requestFocusCallback(rightNode); + return true; + } + return false; + + case TraversalDirection.up: + case TraversalDirection.down: + return false; + } + } +} From 034de16a32c0fdf3328a72bf45def00c01f7e2c7 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 12:10:43 +0300 Subject: [PATCH 13/33] remove unused `ArrowButtonsFocusTraversalPolicy` in the quran_reading_widgets.dart --- .../widget/reading/quran_reading_widgets.dart | 64 ------------------- 1 file changed, 64 deletions(-) diff --git a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart index 3f467fd6e..5c530290d 100644 --- a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart +++ b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart @@ -250,67 +250,3 @@ Widget buildSvgPicture(SvgPicture svgPicture) { ), ); } - - -class ArrowButtonsFocusTraversalPolicy extends FocusTraversalPolicy { - const ArrowButtonsFocusTraversalPolicy({super.requestFocusCallback}); - - @override - FocusNode? findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction) { - final nodes = currentNode.nearestScope!.traversalDescendants; - return nodes.firstWhereOrNull((node) => node.debugLabel?.contains('left_skip_node') == true); - } - - @override - Iterable sortDescendants(Iterable descendants, FocusNode currentNode) { - // Filter only the arrow button nodes - final arrowNodes = descendants.where((node) => - node.debugLabel?.contains('left_skip_node') == true || - node.debugLabel?.contains('right_skip_node') == true - ).toList(); - - // Sort them so left is first, right is second - mergeSort(arrowNodes, compare: (a, b) { - final aIsLeft = a.debugLabel?.contains('left_skip_node') == true; - final bIsLeft = b.debugLabel?.contains('left_skip_node') == true; - return aIsLeft ? -1 : 1; - }); - - return arrowNodes; - } - - @override - bool inDirection(FocusNode currentNode, TraversalDirection direction) { - final scope = currentNode.nearestScope!; - final descendants = scope.traversalDescendants; - - final leftNode = descendants.firstWhereOrNull( - (node) => node.debugLabel?.contains('left_skip_node') == true - ); - final rightNode = descendants.firstWhereOrNull( - (node) => node.debugLabel?.contains('right_skip_node') == true - ); - - if (leftNode == null || rightNode == null) return false; - - switch (direction) { - case TraversalDirection.left: - if (currentNode == rightNode) { - requestFocusCallback(leftNode); - return true; - } - return false; - - case TraversalDirection.right: - if (currentNode == leftNode) { - requestFocusCallback(rightNode); - return true; - } - return false; - - case TraversalDirection.up: - case TraversalDirection.down: - return false; - } - } -} From 364267d131e0fde6cdb7e19efd3d74a8f39becb3 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 12:47:27 +0300 Subject: [PATCH 14/33] fix: resolve Positioned widget conflicts and improve focus navigation - Remove nested Positioned widgets causing render conflicts - Fix focus navigation system in reading screen: * Add proper FocusTraversalOrder for all interactive elements * Implement custom ArrowButtonsFocusTraversalPolicy * Add keyboard navigation support (arrows, tab, enter/space) - Reorganize widget tree structure to prevent parent data conflicts - Improve navigation button layout and accessibility - Fix RTL/LTR direction handling in navigation buttons --- .../quran/reading/quran_reading_screen.dart | 95 +++++-------------- 1 file changed, 26 insertions(+), 69 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index fe3ce261b..43a9af376 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -119,18 +119,6 @@ class _QuranReadingScreenState extends ConsumerState { } }); - // _leftSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); - // _rightSkipButtonFocusNode.onKeyEvent = (node, event) => _handleSwitcherFocusGroupNode(node, event); - // _switchScreenViewFocusNode.onKeyEvent = - // (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); - // _portraitModePageSelectorFocusNode.onKeyEvent = - // (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); - // _switchQuranModeNode.onKeyEvent = (node, event) => _handlePageScrollDownFocusGroupNode(node, event, _isRotated); - // _portraitModeBackButtonFocusNode.onKeyEvent = - // (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated); - // _portraitModeSwitchQuranFocusNode.onKeyEvent = - // (node, event) => _handlePageScrollUpFocusGroupNode(node, event, _isRotated); - final autoReadingState = ref.watch(autoScrollNotifierProvider); return WillPopScope( @@ -179,9 +167,7 @@ class _QuranReadingScreenState extends ConsumerState { final color = Theme.of(context).primaryColor; return quranReadingState.when( loading: () => Center( - child: CircularProgressIndicator( - color: color, - ), + child: CircularProgressIndicator(color: color), ), error: (error, s) { final errorLocalized = S.of(context).error; @@ -199,18 +185,9 @@ class _QuranReadingScreenState extends ConsumerState { if (!isPortrait) ...[ // Back button - Positioned( - top: 10, - left: 10, - child: FocusTraversalOrder( - order: NumericFocusOrder(1), - child: buildBackButton( - isPortrait, - userPrefs, - context, - _backButtonFocusNode, - ), - ), + FocusTraversalOrder( + order: NumericFocusOrder(1), + child: buildBackButton(isPortrait, userPrefs, context, _backButtonFocusNode), ), // Navigation buttons @@ -222,36 +199,22 @@ class _QuranReadingScreenState extends ConsumerState { child: Stack( children: [ // Left button - Positioned( - left: 10, - top: 0, - bottom: 0, - child: Center( - child: FocusTraversalOrder( - order: NumericFocusOrder(2), - child: buildLeftSwitchButton( - context, - _leftSkipButtonFocusNode, - () => _scrollPageList(ScrollDirection.reverse, isPortrait), - ), - ), + FocusTraversalOrder( + order: NumericFocusOrder(2), + child: buildLeftSwitchButton( + context, + _leftSkipButtonFocusNode, + () => _scrollPageList(ScrollDirection.reverse, isPortrait), ), ), // Right button - Positioned( - right: 10, - top: 0, - bottom: 0, - child: Center( - child: FocusTraversalOrder( - order: NumericFocusOrder(3), - child: buildRightSwitchButton( - context, - _rightSkipButtonFocusNode, - () => _scrollPageList(ScrollDirection.forward, isPortrait), - ), - ), + FocusTraversalOrder( + order: NumericFocusOrder(3), + child: buildRightSwitchButton( + context, + _rightSkipButtonFocusNode, + () => _scrollPageList(ScrollDirection.forward, isPortrait), ), ), ], @@ -259,26 +222,20 @@ class _QuranReadingScreenState extends ConsumerState { ), // Page selector - Positioned( - top: 60, - left: 0, - right: 0, - child: FocusTraversalOrder( - order: NumericFocusOrder(4), - child: buildPageNumberIndicator( - quranReadingState, - isPortrait, - context, - _portraitModePageSelectorFocusNode, - _showPageSelector, - ), + FocusTraversalOrder( + order: NumericFocusOrder(4), + child: buildPageNumberIndicator( + quranReadingState, + isPortrait, + context, + _portraitModePageSelectorFocusNode, + _showPageSelector, ), ), // Moshaf selector - Positioned( - top: 10, - right: 60, + FocusTraversalOrder( + order: NumericFocusOrder(5), child: buildMoshafSelector( isPortrait, context, From d960fc5a996036550efe4185575edff1eddbae3d Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 12:49:57 +0300 Subject: [PATCH 15/33] remove the unnecessary `FocusTraversalGroup` and order --- .../quran/reading/quran_reading_screen.dart | 58 ++++++++----------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 43a9af376..5cfe215f3 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -133,21 +133,18 @@ class _QuranReadingScreenState extends ConsumerState { }); return RotatedBox( quarterTurns: state.isRotated ? -1 : 0, - child: FocusTraversalGroup( - policy: OrderedTraversalPolicy(), - 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), + 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), ), ), ); @@ -185,10 +182,7 @@ class _QuranReadingScreenState extends ConsumerState { if (!isPortrait) ...[ // Back button - FocusTraversalOrder( - order: NumericFocusOrder(1), - child: buildBackButton(isPortrait, userPrefs, context, _backButtonFocusNode), - ), + buildBackButton(isPortrait, userPrefs, context, _backButtonFocusNode), // Navigation buttons FocusTraversalGroup( @@ -222,26 +216,20 @@ class _QuranReadingScreenState extends ConsumerState { ), // Page selector - FocusTraversalOrder( - order: NumericFocusOrder(4), - child: buildPageNumberIndicator( - quranReadingState, - isPortrait, - context, - _portraitModePageSelectorFocusNode, - _showPageSelector, - ), + buildPageNumberIndicator( + quranReadingState, + isPortrait, + context, + _portraitModePageSelectorFocusNode, + _showPageSelector, ), // Moshaf selector - FocusTraversalOrder( - order: NumericFocusOrder(5), - child: buildMoshafSelector( - isPortrait, - context, - _switchQuranFocusNode, - _isThereCurrentDialogShowing(context), - ), + buildMoshafSelector( + isPortrait, + context, + _switchQuranFocusNode, + _isThereCurrentDialogShowing(context), ), // Surah title From 7daa7b5e15526823fa2794b7a4cdca23d5c7d893 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 14:31:55 +0300 Subject: [PATCH 16/33] refactor: remove `QuranFocusTraversalPolicy` class from `quran_floating_action_buttons.dart` --- .../widget/quran_floating_action_buttons.dart | 118 ------------------ 1 file changed, 118 deletions(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index ea615df75..5fcef75e7 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -390,121 +390,3 @@ class _OrientationToggleButton extends ConsumerWidget { ); } } - - -class QuranFocusTraversalPolicy extends ReadingOrderTraversalPolicy { - @override - bool inDirection(FocusNode currentNode, TraversalDirection direction) { - final FocusScopeNode scope = currentNode.nearestScope!; - final List nodes = scope.traversalDescendants.toList(); - - // Get specific nodes by their debug labels with null safety - final leftArrowNode = nodes.firstWhereOrNull( - (node) => node.debugLabel?.contains('left_skip_node') == true, - ); - final rightArrowNode = nodes.firstWhereOrNull( - (node) => node.debugLabel?.contains('right_skip_node') == true, - ); - final playButtonNode = nodes.firstWhereOrNull( - (node) => node.debugLabel?.contains('switch_to_play_quran_node') == true, - ); - final orientationButtonNode = nodes.firstWhereOrNull( - (node) => node.debugLabel?.contains('switch_screen_view_node') == true, - ); - final modeButtonNode = nodes.firstWhereOrNull( - (node) => node.debugLabel?.contains('switch_quran_mode_node') == true, - ); - final hafsFocusNode = nodes.firstWhereOrNull( - (node) => node.debugLabel?.contains('go_to_hafs_node') == true, - ); - final pageSelectorNode = nodes.firstWhereOrNull( - (node) => node.debugLabel?.contains('page_selector_node') == true, - ); - final backButtonNode = nodes.firstWhereOrNull( - (node) => node.debugLabel?.contains('back_button_node') == true, - ); - - // Handle navigation between arrow buttons and related controls - if (currentNode == leftArrowNode || currentNode == rightArrowNode || - currentNode == hafsFocusNode || currentNode == pageSelectorNode || - currentNode == backButtonNode) { - switch (direction) { - case TraversalDirection.left: - if (currentNode == rightArrowNode && leftArrowNode != null) { - requestFocusCallback(leftArrowNode); - return true; - } else if (currentNode == hafsFocusNode && pageSelectorNode != null) { - requestFocusCallback(pageSelectorNode); - return true; - } - return false; - - case TraversalDirection.right: - if (currentNode == leftArrowNode && rightArrowNode != null) { - requestFocusCallback(rightArrowNode); - return true; - } else if (currentNode == pageSelectorNode && hafsFocusNode != null) { - requestFocusCallback(hafsFocusNode); - return true; - } - return false; - - case TraversalDirection.down: - if (currentNode == rightArrowNode && playButtonNode != null) { - requestFocusCallback(playButtonNode); - return true; - } else if (currentNode == leftArrowNode && hafsFocusNode != null) { - requestFocusCallback(hafsFocusNode); - return true; - } - return false; - - case TraversalDirection.up: - if (currentNode == leftArrowNode && backButtonNode != null) { - requestFocusCallback(backButtonNode); - return true; - } - return false; - } - } - - // Handle navigation in FAB column - if (currentNode == playButtonNode || - currentNode == orientationButtonNode || - currentNode == modeButtonNode) { - switch (direction) { - case TraversalDirection.up: - if (currentNode == orientationButtonNode && playButtonNode != null) { - requestFocusCallback(playButtonNode); - return true; - } else if (currentNode == modeButtonNode && orientationButtonNode != null) { - requestFocusCallback(orientationButtonNode); - return true; - } - return false; - - case TraversalDirection.down: - if (currentNode == playButtonNode && orientationButtonNode != null) { - requestFocusCallback(orientationButtonNode); - return true; - } else if (currentNode == orientationButtonNode && modeButtonNode != null) { - requestFocusCallback(modeButtonNode); - return true; - } - return false; - - case TraversalDirection.left: - if (currentNode == playButtonNode && rightArrowNode != null) { - requestFocusCallback(rightArrowNode); - return true; - } - return false; - - case TraversalDirection.right: - return false; - } - } - - return super.inDirection(currentNode, direction); - } -} From 4021ecb8007dd4863bdead118d374fecdd59f3ba Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 14:33:56 +0300 Subject: [PATCH 17/33] refactor: implement strategy pattern for Quran reading view and focus management Introduced the `QuranViewStrategy` abstract class and created two concrete strategies, `AutoScrollViewStrategy` and `NormalViewStrategy`, to handle view and control layout for different Quran reading modes. Replaced previous inline focus management with a new `FocusNodes` helper class for organizing focus nodes. Refactored loading and error indicators into separate widget methods for cleaner code structure. This update enhances readability and allows for easier expansion of view strategies in the future. --- .../quran/reading/quran_reading_screen.dart | 310 ++++++++++++------ 1 file changed, 202 insertions(+), 108 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 5cfe215f3..c2d4006de 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -21,10 +21,164 @@ import 'package:mawaqit/src/pages/quran/widget/download_quran_popup.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; import 'package:provider/provider.dart' as provider; -import 'package:sizer/sizer.dart'; - import 'package:mawaqit/src/pages/quran/widget/reading/quran_reading_page_selector.dart'; +abstract class QuranViewStrategy { + Widget buildView(QuranReadingState state, WidgetRef ref, BuildContext context); + + List buildControls( + BuildContext context, + QuranReadingState state, + UserPreferencesManager userPrefs, + bool isPortrait, + FocusNodes focusNodes, + Function(ScrollDirection, bool) onScroll, + Function(BuildContext, int, int, bool) showPageSelector, + ); +} + +// Helper class to organize focus nodes +class FocusNodes { + final FocusNode backButtonNode; + final FocusNode leftSkipNode; + final FocusNode rightSkipNode; + final FocusNode pageSelectorNode; + final FocusNode switchQuranNode; + + FocusNodes({ + required this.backButtonNode, + required this.leftSkipNode, + required this.rightSkipNode, + required this.pageSelectorNode, + required this.switchQuranNode, + }); +} + +class AutoScrollViewStrategy implements QuranViewStrategy { + final AutoScrollState autoScrollState; + + AutoScrollViewStrategy(this.autoScrollState); + + @override + Widget buildView(QuranReadingState state, WidgetRef ref, BuildContext context) { + return ListView.builder( + controller: autoScrollState.scrollController, + itemCount: state.totalPages, + itemBuilder: (context, index) { + return LayoutBuilder( + builder: (context, constraints) { + final pageHeight = + constraints.maxHeight.isInfinite ? MediaQuery.of(context).size.height : constraints.maxHeight; + return SizedBox( + width: constraints.maxWidth, + height: pageHeight, + child: buildSvgPicture(state.svgs[index]), + ); + }, + ); + }, + ); + } + + @override + List buildControls( + BuildContext context, + QuranReadingState state, + UserPreferencesManager userPrefs, + bool isPortrait, + FocusNodes focusNodes, + Function(ScrollDirection, bool) onScroll, + Function(BuildContext, int, int, bool) showPageSelector, + ) { + return [ + buildBackButton( + isPortrait, + userPrefs, + context, + focusNodes.backButtonNode, + ), + ]; + } +} + +class NormalViewStrategy implements QuranViewStrategy { + final bool isPortrait; + + NormalViewStrategy(this.isPortrait); + + @override + Widget buildView(QuranReadingState state, WidgetRef ref, BuildContext context) { + return isPortrait ? buildVerticalPageView(state, ref) : buildHorizontalPageView(state, ref, context); + } + + @override + List buildControls( + BuildContext context, + QuranReadingState state, + UserPreferencesManager userPrefs, + bool isPortrait, + FocusNodes focusNodes, + Function(ScrollDirection, bool) onScroll, + Function(BuildContext, int, int, bool) showPageSelector, + ) { + return [ + buildBackButton( + isPortrait, + userPrefs, + context, + focusNodes.backButtonNode, + ), + _buildNavigationButtons( + context, + focusNodes, + onScroll, + isPortrait, + ), + buildPageNumberIndicator( + state, + isPortrait, + context, + focusNodes.pageSelectorNode, + showPageSelector, + ), + buildMoshafSelector( + isPortrait, + context, + focusNodes.switchQuranNode, + false, + ), + ]; + } + + Widget _buildNavigationButtons( + BuildContext context, + FocusNodes focusNodes, + Function(ScrollDirection, bool) onScroll, + bool isPortrait, + ) { + return FocusTraversalGroup( + policy: ArrowButtonsFocusTraversalPolicy( + backButtonNode: focusNodes.backButtonNode, + pageSelectorNode: focusNodes.pageSelectorNode, + ), + child: Stack( + children: [ + buildLeftSwitchButton( + context, + focusNodes.leftSkipNode, + () => onScroll(ScrollDirection.reverse, isPortrait), + ), + buildRightSwitchButton( + context, + focusNodes.rightSkipNode, + () => onScroll(ScrollDirection.forward, isPortrait), + ), + ], + ), + ); + } +} + class QuranReadingScreen extends ConsumerStatefulWidget { const QuranReadingScreen({super.key}); @@ -149,91 +303,51 @@ class _QuranReadingScreenState extends ConsumerState { ), ); }, - loading: () => const CircularProgressIndicator(), + loading: () => SizedBox(), error: (error, stack) => const Icon(Icons.error), ), ); } Widget _buildBody( - AsyncValue quranReadingState, - bool isPortrait, - UserPreferencesManager userPrefs, - AutoScrollState autoScrollState, - ) { - final color = Theme.of(context).primaryColor; + AsyncValue quranReadingState, + bool isPortrait, + UserPreferencesManager userPrefs, + AutoScrollState autoScrollState, + ) { return quranReadingState.when( - loading: () => Center( - child: CircularProgressIndicator(color: color), - ), - error: (error, s) { - final errorLocalized = S.of(context).error; - return Center(child: Text('$errorLocalized: $error')); - }, - data: (quranReadingState) { + loading: () => _buildLoadingIndicator(), + error: (error, s) => _buildErrorIndicator(error), + data: (state) { + // Initialize the appropriate strategy + final viewStrategy = + autoScrollState.isSinglePageView ? AutoScrollViewStrategy(autoScrollState) : NormalViewStrategy(isPortrait); + + // Create focus nodes bundle + final focusNodes = FocusNodes( + backButtonNode: _backButtonFocusNode, + leftSkipNode: _leftSkipButtonFocusNode, + rightSkipNode: _rightSkipButtonFocusNode, + pageSelectorNode: _portraitModePageSelectorFocusNode, + switchQuranNode: _switchQuranFocusNode, + ); + return Stack( children: [ - // Main content (Quran pages) - autoScrollState.isSinglePageView - ? buildAutoScrollView(quranReadingState, ref, autoScrollState) - : isPortrait - ? buildVerticalPageView(quranReadingState, ref) - : buildHorizontalPageView(quranReadingState, ref, context), + // Main content + viewStrategy.buildView(state, ref, context), + // Controls overlay if (!isPortrait) ...[ - // Back button - buildBackButton(isPortrait, userPrefs, context, _backButtonFocusNode), - - // Navigation buttons - FocusTraversalGroup( - policy: ArrowButtonsFocusTraversalPolicy( - backButtonNode: _backButtonFocusNode, - pageSelectorNode: _portraitModePageSelectorFocusNode, - ), - child: Stack( - children: [ - // Left button - FocusTraversalOrder( - order: NumericFocusOrder(2), - child: buildLeftSwitchButton( - context, - _leftSkipButtonFocusNode, - () => _scrollPageList(ScrollDirection.reverse, isPortrait), - ), - ), - - // Right button - FocusTraversalOrder( - order: NumericFocusOrder(3), - child: buildRightSwitchButton( - context, - _rightSkipButtonFocusNode, - () => _scrollPageList(ScrollDirection.forward, isPortrait), - ), - ), - ], - ), - ), - - // Page selector - buildPageNumberIndicator( - quranReadingState, - isPortrait, + ...viewStrategy.buildControls( context, - _portraitModePageSelectorFocusNode, - _showPageSelector, - ), - - // Moshaf selector - buildMoshafSelector( + state, + userPrefs, isPortrait, - context, - _switchQuranFocusNode, - _isThereCurrentDialogShowing(context), + focusNodes, + _scrollPageList, + _showPageSelector, ), - - // Surah title - buildShowSurah(quranReadingState), ], ], ); @@ -241,40 +355,21 @@ class _QuranReadingScreenState extends ConsumerState { ); } - Align buildShowSurah(QuranReadingState quranReadingState) { - return Align( - alignment: Alignment.topCenter, - child: Padding( - padding: EdgeInsets.only(top: 0.3.h), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () { - ref.read(quranReadingNotifierProvider.notifier).getAllSuwarPage(); - showSurahSelector(context, quranReadingState.currentPage); - }, - borderRadius: BorderRadius.circular(20), - child: Container( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.4), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - quranReadingState.currentSurahName, - style: TextStyle( - color: Colors.white, - fontSize: 8.sp, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), + Widget _buildLoadingIndicator() { + return Center( + child: CircularProgressIndicator( + color: Theme.of(context).primaryColor, ), ); } + Widget _buildErrorIndicator(Object error) { + final errorLocalized = S.of(context).error; + return Center( + child: Text('$errorLocalized: $error'), + ); + } + Widget buildAutoScrollView( QuranReadingState quranReadingState, WidgetRef ref, @@ -336,7 +431,6 @@ class _QuranReadingScreenState extends ConsumerState { bool _isThereCurrentDialogShowing(BuildContext context) => ModalRoute.of(context)?.isCurrent != true; } - class ArrowButtonsFocusTraversalPolicy extends FocusTraversalPolicy { final FocusNode backButtonNode; final FocusNode pageSelectorNode; @@ -374,10 +468,10 @@ class ArrowButtonsFocusTraversalPolicy extends FocusTraversalPolicy { @override Iterable sortDescendants(Iterable descendants, FocusNode currentNode) { - final arrowNodes = descendants.where((node) => - node.debugLabel?.contains('left_skip_node') == true || - node.debugLabel?.contains('right_skip_node') == true - ).toList(); + final arrowNodes = descendants + .where((node) => + node.debugLabel?.contains('left_skip_node') == true || node.debugLabel?.contains('right_skip_node') == true) + .toList(); mergeSort(arrowNodes, compare: (a, b) { final aIsLeft = a.debugLabel?.contains('left_skip_node') == true; From 499848c125bf92821821f1f73fc44ba7dd2b6e47 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 15:07:28 +0300 Subject: [PATCH 18/33] refactor: add font size and speed controls for Quran auto-scrolling mode - Updated `autoScrollSpeed` default value in `AutoScrollState` to 0.1 for a slower starting speed. - Added `cycleFontSize` and `cycleSpeed` methods in `AutoScrollNotifier` to allow cycling through font sizes and scroll speeds with a single button, improving user control and simplifying UI. - Refactored `_FontSizeControls` and `_SpeedControls` widgets to use a single `_ActionButton` for adjusting font size and speed, displaying current values in tooltips. - Re-introduced `_ActionButton` class with autofocus support for enhanced focus management. --- .../widget/quran_floating_action_buttons.dart | 163 +++++++----------- .../auto_reading/auto_reading_notifier.dart | 21 +++ .../auto_reading/auto_reading_state.dart | 2 +- 3 files changed, 89 insertions(+), 97 deletions(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 5fcef75e7..496b9bac4 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -202,42 +202,6 @@ class _PlayPauseButton extends ConsumerWidget { } } -class _ActionButton extends StatelessWidget { - final bool isPortrait; - final IconData icon; - final VoidCallback onPressed; - final String? tooltip; - final FocusNode? focusNode; - - const _ActionButton({ - required this.isPortrait, - required this.icon, - required this.onPressed, - this.tooltip, - this.focusNode, - }); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: focusNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - icon, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: onPressed, - heroTag: null, - tooltip: tooltip, - ), - ); - } -} - class _AutoScrollingReadingMode extends ConsumerWidget { final bool isPortrait; final QuranReadingState quranReadingState; @@ -255,11 +219,15 @@ class _AutoScrollingReadingMode extends ConsumerWidget { return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - _FontSizeControls(isPortrait: isPortrait), + _FontSizeControls( + isPortrait: isPortrait, + fontSize: autoScrollState.fontSize, + ), SizedBox(height: 1.h), _SpeedControls( quranReadingState: quranReadingState, isPortrait: isPortrait, + speed: autoScrollState.autoScrollSpeed, ), SizedBox(height: 1.h), _PlayPauseButton( @@ -273,27 +241,21 @@ class _AutoScrollingReadingMode extends ConsumerWidget { class _FontSizeControls extends ConsumerWidget { final bool isPortrait; + final double fontSize; - const _FontSizeControls({required this.isPortrait}); + const _FontSizeControls({ + required this.isPortrait, + required this.fontSize, + }); @override Widget build(BuildContext context, WidgetRef ref) { - return Column( - children: [ - _ActionButton( - isPortrait: isPortrait, - icon: Icons.remove, - onPressed: () => ref.read(autoScrollNotifierProvider.notifier).decreaseFontSize(), - tooltip: 'Decrease Font Size', - ), - SizedBox(width: 1.h), - _ActionButton( - isPortrait: isPortrait, - icon: Icons.add, - onPressed: () => ref.read(autoScrollNotifierProvider.notifier).increaseFontSize(), - tooltip: 'Increase Font Size', - ), - ], + return _ActionButton( + isPortrait: isPortrait, + icon: Icons.text_fields, + onPressed: () => ref.read(autoScrollNotifierProvider.notifier).cycleFontSize(), + tooltip: 'Font Size: ${(fontSize * 100).toInt()}%', + autoFocus: true, ); } } @@ -301,56 +263,65 @@ class _FontSizeControls extends ConsumerWidget { class _SpeedControls extends ConsumerWidget { final QuranReadingState quranReadingState; final bool isPortrait; + final double speed; const _SpeedControls({ required this.quranReadingState, required this.isPortrait, + required this.speed, }); @override Widget build(BuildContext context, WidgetRef ref) { - return Positioned( - right: 16, - bottom: isPortrait ? 100 : 16, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - FloatingActionButton( - mini: true, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - Icons.add, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: () { - final pageHeight = MediaQuery.of(context).size.height; - ref.read(autoScrollNotifierProvider.notifier).increaseSpeed( - quranReadingState.currentPage, - pageHeight, - ); - }, - heroTag: 'increase_speed', - ), - SizedBox(height: 8), - FloatingActionButton( - mini: true, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - Icons.remove, - color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, - ), - onPressed: () { - final pageHeight = MediaQuery.of(context).size.height; - ref.read(autoScrollNotifierProvider.notifier).decreaseSpeed( - quranReadingState.currentPage, - pageHeight, - ); - }, - heroTag: 'decrease_speed', - ), - ], + return _ActionButton( + isPortrait: isPortrait, + icon: Icons.speed, + onPressed: () { + final pageHeight = MediaQuery.of(context).size.height; + ref.read(autoScrollNotifierProvider.notifier).cycleSpeed( + quranReadingState.currentPage, + pageHeight, + ); + }, + tooltip: 'Speed: ${(speed * 100).toInt()}%', + ); + } +} + +class _ActionButton extends StatelessWidget { + final bool isPortrait; + final IconData icon; + final VoidCallback onPressed; + final String? tooltip; + final FocusNode? focusNode; + bool autoFocus; + + _ActionButton({ + required this.isPortrait, + required this.icon, + required this.onPressed, + this.tooltip, + this.focusNode, + this.autoFocus = false, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: isPortrait ? 35.sp : 30.sp, + height: isPortrait ? 35.sp : 30.sp, + child: FloatingActionButton( + autofocus: autoFocus, + focusNode: focusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + icon, + color: Colors.white, + size: isPortrait ? 20.sp : 15.sp, + ), + onPressed: onPressed, + heroTag: null, + tooltip: tooltip, ), ); } diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart index f3a1be2ad..f6ae06bea 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart @@ -134,6 +134,27 @@ class AutoScrollNotifier extends AutoDisposeNotifier { if (newFontSize < 1.0) newFontSize = 1.0; state = state.copyWith(fontSize: newFontSize); } + + void cycleFontSize() { + if (state.fontSize >= state.maxFontSize) { + state = state.copyWith(fontSize: 1.0); + } else { + state = state.copyWith(fontSize: state.fontSize + 0.2); + } + } + + void cycleSpeed(int currentPage, double pageHeight) { + double newSpeed; + if (state.autoScrollSpeed >= 0.5) { + newSpeed = 0.1; + } else { + newSpeed = state.autoScrollSpeed + 0.1; + } + state = state.copyWith(autoScrollSpeed: newSpeed); + if (state.isAutoScrolling) { + startAutoScroll(currentPage, pageHeight); + } + } } final autoScrollNotifierProvider = AutoDisposeNotifierProvider( diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart index a0e7b0aaf..8ccd209b5 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart @@ -11,7 +11,7 @@ class AutoScrollState { AutoScrollState({ required this.scrollController, this.isSinglePageView = false, - this.autoScrollSpeed = 1.0, + this.autoScrollSpeed = 0.1, this.isVisible = true, this.fontSize = 1.0, this.maxFontSize = 3.0, From a4805ce0d25e1b7238e2554e4f08a549b8b6bd93 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 17:19:56 +0300 Subject: [PATCH 19/33] refactor: Quran reading widgets for improved modularity and maintainability - Converted functions in `quran_reading_widgets.dart` into distinct `ConsumerWidget` classes: - `VerticalPageViewWidget`, `HorizontalPageViewWidget` - `RightSwitchButtonWidget`, `LeftSwitchButtonWidget` - `PageNumberIndicatorWidget`, `MoshafSelectorPositionedWidget` - `BackButtonWidget`, `SvgPictureWidget` --- .../quran/reading/quran_reading_screen.dart | 62 +-- .../widget/reading/quran_reading_widgets.dart | 483 +++++++++++------- 2 files changed, 330 insertions(+), 215 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index c2d4006de..80a2c7b91 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -72,7 +72,7 @@ class AutoScrollViewStrategy implements QuranViewStrategy { return SizedBox( width: constraints.maxWidth, height: pageHeight, - child: buildSvgPicture(state.svgs[index]), + child: SvgPictureWidget(svgPicture: state.svgs[index]), ); }, ); @@ -91,11 +91,10 @@ class AutoScrollViewStrategy implements QuranViewStrategy { Function(BuildContext, int, int, bool) showPageSelector, ) { return [ - buildBackButton( - isPortrait, - userPrefs, - context, - focusNodes.backButtonNode, + BackButtonWidget( + isPortrait: isPortrait, + userPrefs: userPrefs, + focusNode: focusNodes.backButtonNode, ), ]; } @@ -108,7 +107,13 @@ class NormalViewStrategy implements QuranViewStrategy { @override Widget buildView(QuranReadingState state, WidgetRef ref, BuildContext context) { - return isPortrait ? buildVerticalPageView(state, ref) : buildHorizontalPageView(state, ref, context); + return isPortrait + ? VerticalPageViewWidget( + quranReadingState: state, + ) + : HorizontalPageViewWidget( + quranReadingState: state, + ); } @override @@ -122,11 +127,10 @@ class NormalViewStrategy implements QuranViewStrategy { Function(BuildContext, int, int, bool) showPageSelector, ) { return [ - buildBackButton( - isPortrait, - userPrefs, - context, - focusNodes.backButtonNode, + BackButtonWidget( + isPortrait: isPortrait, + userPrefs: userPrefs, + focusNode: focusNodes.backButtonNode, ), _buildNavigationButtons( context, @@ -134,18 +138,16 @@ class NormalViewStrategy implements QuranViewStrategy { onScroll, isPortrait, ), - buildPageNumberIndicator( - state, - isPortrait, - context, - focusNodes.pageSelectorNode, - showPageSelector, + PageNumberIndicatorWidget( + quranReadingState: state, + focusNode: focusNodes.pageSelectorNode, + isPortrait: isPortrait, + showPageSelector: showPageSelector, ), - buildMoshafSelector( - isPortrait, - context, - focusNodes.switchQuranNode, - false, + MoshafSelectorPositionedWidget( + isPortrait: isPortrait, + focusNode: focusNodes.switchQuranNode, + isThereCurrentDialogShowing: false, ), ]; } @@ -163,15 +165,13 @@ class NormalViewStrategy implements QuranViewStrategy { ), child: Stack( children: [ - buildLeftSwitchButton( - context, - focusNodes.leftSkipNode, - () => onScroll(ScrollDirection.reverse, isPortrait), + LeftSwitchButtonWidget( + focusNode: focusNodes.leftSkipNode, + onPressed: () => onScroll(ScrollDirection.reverse, isPortrait), ), - buildRightSwitchButton( - context, - focusNodes.rightSkipNode, - () => onScroll(ScrollDirection.forward, isPortrait), + RightSwitchButtonWidget( + focusNode: focusNodes.rightSkipNode, + onPressed: () => onScroll(ScrollDirection.forward, isPortrait), ), ], ), diff --git a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart index 5c530290d..eb23ee264 100644 --- a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart +++ b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart @@ -6,205 +6,308 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:mawaqit/i18n/l10n.dart'; import 'package:mawaqit/src/pages/quran/widget/reading/moshaf_selector.dart'; import 'package:mawaqit/src/pages/quran/widget/switch_button.dart'; -import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; import 'package:mawaqit/src/services/user_preferences_manager.dart'; +import 'package:mawaqit/src/state_management/quran/reading/quran_reading_state.dart'; import 'package:sizer/sizer.dart'; -import '../../../../state_management/quran/reading/quran_reading_notifer.dart'; - -Widget buildVerticalPageView(QuranReadingState quranReadingState, WidgetRef ref) { - return PageView.builder( - scrollDirection: Axis.vertical, - controller: quranReadingState.pageController, - onPageChanged: (index) { - if (index != quranReadingState.currentPage) { - ref.read(quranReadingNotifierProvider.notifier).updatePage(index, isPortairt: true); - } - }, - itemCount: quranReadingState.totalPages, - itemBuilder: (context, index) { - return LayoutBuilder( - builder: (context, constraints) { - final pageWidth = constraints.maxWidth; - final pageHeight = constraints.maxHeight; - - return Stack( - children: [ - Positioned.fill( - child: FittedBox( - fit: BoxFit.contain, - child: SizedBox( - width: pageWidth + 150, - height: pageHeight + 100, - child: buildSvgPicture( - quranReadingState.svgs[index % quranReadingState.svgs.length], +import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; + +import 'package:mawaqit/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart'; + +class VerticalPageViewWidget extends ConsumerWidget { + final QuranReadingState quranReadingState; + + const VerticalPageViewWidget({ + super.key, + required this.quranReadingState, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return PageView.builder( + scrollDirection: Axis.vertical, + controller: quranReadingState.pageController, + onPageChanged: (index) { + if (index != quranReadingState.currentPage) { + ref + .read(quranReadingNotifierProvider.notifier) + .updatePage(index, isPortairt: true); + } + }, + itemCount: quranReadingState.totalPages, + itemBuilder: (context, index) { + return LayoutBuilder( + builder: (context, constraints) { + final pageWidth = constraints.maxWidth; + final pageHeight = constraints.maxHeight; + + return Stack( + children: [ + Positioned.fill( + child: FittedBox( + fit: BoxFit.contain, + child: SizedBox( + width: pageWidth + 150, + height: pageHeight + 100, + child: SvgPictureWidget( + svgPicture: quranReadingState + .svgs[index % quranReadingState.svgs.length], + ), ), ), ), - ), - ], - ); - }, - ); - }, - ); + ], + ); + }, + ); + }, + ); + } } -Widget buildHorizontalPageView(QuranReadingState quranReadingState, WidgetRef ref, BuildContext context) { - return PageView.builder( - reverse: Directionality.of(context) == TextDirection.ltr ? true : false, - controller: quranReadingState.pageController, - onPageChanged: (index) { - final actualPage = index * 2; - if (actualPage != quranReadingState.currentPage) { - ref.read(quranReadingNotifierProvider.notifier).updatePage(actualPage); - } - }, - itemCount: (quranReadingState.totalPages / 2).ceil(), - itemBuilder: (context, index) { - return LayoutBuilder( - builder: (context, constraints) { - final pageWidth = constraints.maxWidth / 2; - final pageHeight = constraints.maxHeight; - final bottomPadding = pageHeight * 0.05; - - final leftPageIndex = index * 2; - final rightPageIndex = leftPageIndex + 1; - return Stack( - children: [ - if (rightPageIndex < quranReadingState.svgs.length) - Positioned( - left: 12.w, - top: 0, - bottom: bottomPadding, - width: pageWidth * 0.9, - child: buildSvgPicture( - quranReadingState.svgs[rightPageIndex % quranReadingState.svgs.length], +class HorizontalPageViewWidget extends ConsumerWidget { + final QuranReadingState quranReadingState; + + const HorizontalPageViewWidget({ + super.key, + required this.quranReadingState, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final autoScrollState = ref.watch(autoScrollNotifierProvider); + return PageView.builder( + reverse: Directionality.of(context) == TextDirection.ltr ? true : false, + controller: quranReadingState.pageController, + onPageChanged: (index) { + final actualPage = index * 2; + if (actualPage != quranReadingState.currentPage) { + ref.read(quranReadingNotifierProvider.notifier).updatePage(actualPage); + } + }, + itemCount: (quranReadingState.totalPages / 2).ceil(), + itemBuilder: (context, index) { + return LayoutBuilder( + builder: (context, constraints) { + final pageWidth = constraints.maxWidth / 2; + final pageHeight = constraints.maxHeight; + final bottomPadding = pageHeight * 0.05; + + final leftPageIndex = index * 2; + final rightPageIndex = leftPageIndex + 1; + return Stack( + children: [ + if (rightPageIndex < quranReadingState.svgs.length) + Positioned( + left: 12.w, + top: 0, + bottom: bottomPadding, + width: pageWidth * 0.9, + child: SvgPictureWidget( + svgPicture: quranReadingState + .svgs[rightPageIndex % quranReadingState.svgs.length], + ), ), - ), - if (leftPageIndex < quranReadingState.svgs.length) - Positioned( - right: 12.w, - top: 0, - bottom: bottomPadding, - width: pageWidth * 0.9, - child: buildSvgPicture( - quranReadingState.svgs[leftPageIndex % quranReadingState.svgs.length], + if (leftPageIndex < quranReadingState.svgs.length) + Positioned( + right: 12.w, + top: 0, + bottom: bottomPadding, + width: pageWidth * 0.9, + child: SvgPictureWidget( + svgPicture: quranReadingState + .svgs[leftPageIndex % quranReadingState.svgs.length], + ), ), - ), - ], - ); - }, - ); - }, - ); + ], + ); + }, + ); + }, + ); + } } -Widget buildRightSwitchButton(BuildContext context, FocusNode focusNode, Function() onPressed) { - return FocusTraversalOrder( - order: NumericFocusOrder(2), - child: Positioned( - right: 10, - top: 0, - bottom: 0, - child: SwitchButton( - focusNode: focusNode, - opacity: 0.7, - iconSize: 14.sp, - icon: Directionality.of(context) == TextDirection.ltr ? Icons.arrow_forward_ios : Icons.arrow_back_ios, - onPressed: onPressed, +class RightSwitchButtonWidget extends ConsumerWidget { + final FocusNode focusNode; + final VoidCallback onPressed; + + const RightSwitchButtonWidget({ + super.key, + required this.focusNode, + required this.onPressed, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return FocusTraversalOrder( + order: NumericFocusOrder(2), + child: Positioned( + right: 10, + top: 0, + bottom: 0, + child: SwitchButton( + focusNode: focusNode, + opacity: 0.7, + iconSize: 14.sp, + icon: Directionality.of(context) == TextDirection.ltr + ? Icons.arrow_forward_ios + : Icons.arrow_back_ios, + onPressed: onPressed, + ), ), - ), - ); + ); + } } -Widget buildLeftSwitchButton(BuildContext context, FocusNode focusNode, Function() onPressed) { - return FocusTraversalOrder( - order: NumericFocusOrder(1), - child: Positioned( - left: 10, - top: 0, - bottom: 0, - child: SwitchButton( - focusNode: focusNode, - opacity: 0.7, - iconSize: 14.sp, - icon: Directionality.of(context) != TextDirection.ltr ? Icons.arrow_forward_ios : Icons.arrow_back_ios, - onPressed: onPressed, +class LeftSwitchButtonWidget extends ConsumerWidget { + final FocusNode focusNode; + final VoidCallback onPressed; + + const LeftSwitchButtonWidget({ + Key? key, + required this.focusNode, + required this.onPressed, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return FocusTraversalOrder( + order: NumericFocusOrder(1), + child: Positioned( + left: 10, + top: 0, + bottom: 0, + child: SwitchButton( + focusNode: focusNode, + opacity: 0.7, + iconSize: 14.sp, + icon: Directionality.of(context) != TextDirection.ltr + ? Icons.arrow_forward_ios + : Icons.arrow_back_ios, + onPressed: onPressed, + ), ), - ), - ); + ); + } } -Widget buildPageNumberIndicator(QuranReadingState quranReadingState, bool isPortrait, BuildContext context, - FocusNode focusNode, Function(BuildContext, int, int, bool) showPageSelector) { - return Positioned( - left: 15.w, - right: 15.w, - bottom: isPortrait ? 1.h : 0.5.h, - child: Center( - child: Material( - color: Colors.transparent, - child: InkWell( - focusNode: focusNode, - autofocus: false, - onTap: () => - showPageSelector(context, quranReadingState.totalPages, quranReadingState.currentPage, isPortrait), - borderRadius: BorderRadius.circular(20), - child: Container( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: isPortrait ? 8 : 4), - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.4), - borderRadius: BorderRadius.circular(20), +class PageNumberIndicatorWidget extends ConsumerWidget { + final QuranReadingState quranReadingState; + final bool isPortrait; + final FocusNode focusNode; + final Function(BuildContext, int, int, bool) showPageSelector; + + const PageNumberIndicatorWidget({ + super.key, + required this.quranReadingState, + required this.isPortrait, + required this.focusNode, + required this.showPageSelector, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Positioned( + left: 15.w, + right: 15.w, + bottom: isPortrait ? 1.h : 0.5.h, + child: Center( + child: Material( + color: Colors.transparent, + child: InkWell( + focusNode: focusNode, + autofocus: false, + onTap: () => showPageSelector( + context, + quranReadingState.totalPages, + quranReadingState.currentPage, + isPortrait, ), - child: Text( - isPortrait - ? S - .of(context) - .quranReadingPagePortrait(quranReadingState.currentPage + 1, quranReadingState.totalPages) - : S.of(context).quranReadingPage( - quranReadingState.currentPage + 1, - quranReadingState.currentPage + 2, - quranReadingState.totalPages, - ), - style: TextStyle( - color: Colors.white, - fontSize: 10.sp, - fontWeight: FontWeight.bold, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 16, vertical: isPortrait ? 8 : 4), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.4), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + isPortrait + ? S.of(context).quranReadingPagePortrait( + quranReadingState.currentPage + 1, + quranReadingState.totalPages, + ) + : S.of(context).quranReadingPage( + quranReadingState.currentPage + 1, + quranReadingState.currentPage + 2, + quranReadingState.totalPages, + ), + style: TextStyle( + color: Colors.white, + fontSize: 10.sp, + fontWeight: FontWeight.bold, + ), ), ), ), ), ), - ), - ); + ); + } } -Widget buildMoshafSelector( - bool isPortrait, BuildContext context, FocusNode focusNode, bool isThereCurrentDialogShowing) { - return isPortrait - ? Positioned.directional( - end: 10, - textDirection: Directionality.of(context), - top: 1.h, - child: MoshafSelector( - isAutofocus: !isThereCurrentDialogShowing, - focusNode: focusNode, - ), - ) - : Positioned( - left: 10, - bottom: 0.5.h, - child: MoshafSelector( - isPortrait: false, - isAutofocus: !isThereCurrentDialogShowing, - focusNode: focusNode, - ), - ); +class MoshafSelectorPositionedWidget extends ConsumerWidget { + final bool isPortrait; + final FocusNode focusNode; + final bool isThereCurrentDialogShowing; + + const MoshafSelectorPositionedWidget({ + Key? key, + required this.isPortrait, + required this.focusNode, + required this.isThereCurrentDialogShowing, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return isPortrait + ? Positioned.directional( + end: 10, + textDirection: Directionality.of(context), + top: 1.h, + child: MoshafSelector( + isAutofocus: !isThereCurrentDialogShowing, + focusNode: focusNode, + ), + ) + : Positioned( + left: 10, + bottom: 0.5.h, + child: MoshafSelector( + isPortrait: false, + isAutofocus: !isThereCurrentDialogShowing, + focusNode: focusNode, + ), + ); + } } -Widget buildBackButton(bool isPortrait, UserPreferencesManager userPrefs, BuildContext context, FocusNode focusNode) { - return Positioned.directional( +class BackButtonWidget extends ConsumerWidget { + final bool isPortrait; + final UserPreferencesManager userPrefs; + final FocusNode focusNode; + + const BackButtonWidget({ + Key? key, + required this.isPortrait, + required this.userPrefs, + required this.focusNode, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Positioned.directional( start: 10, top: 10, textDirection: Directionality.of(context), @@ -234,19 +337,31 @@ Widget buildBackButton(bool isPortrait, UserPreferencesManager userPrefs, BuildC ), ), ), - )); + ), + ); + } } -Widget buildSvgPicture(SvgPicture svgPicture) { - return Container( - color: Colors.white, - padding: EdgeInsets.all(32.0), - child: SvgPicture( - svgPicture.bytesLoader, - fit: BoxFit.contain, - width: double.infinity, - height: double.infinity, - alignment: Alignment.center, - ), - ); +class SvgPictureWidget extends StatelessWidget { + final SvgPicture svgPicture; + + const SvgPictureWidget({ + super.key, + required this.svgPicture, + }); + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.white, + padding: EdgeInsets.all(32.0), + child: SvgPicture( + svgPicture.bytesLoader, + fit: BoxFit.contain, + width: double.infinity, + height: double.infinity, + alignment: Alignment.center, + ), + ); + } } From 16966b032d52f34cbd73b2fe2803e5ade7d1d214 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 17:40:43 +0300 Subject: [PATCH 20/33] feat: add scaling the size of the pages with the font --- .../quran/reading/quran_reading_screen.dart | 20 +++++++++---------- .../widget/reading/quran_reading_widgets.dart | 8 ++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 80a2c7b91..d0219734f 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -61,25 +61,25 @@ class AutoScrollViewStrategy implements QuranViewStrategy { @override Widget buildView(QuranReadingState state, WidgetRef ref, BuildContext context) { + final scalingFactor = autoScrollState.fontSize; + return ListView.builder( controller: autoScrollState.scrollController, itemCount: state.totalPages, itemBuilder: (context, index) { - return LayoutBuilder( - builder: (context, constraints) { - final pageHeight = - constraints.maxHeight.isInfinite ? MediaQuery.of(context).size.height : constraints.maxHeight; - return SizedBox( - width: constraints.maxWidth, - height: pageHeight, - child: SvgPictureWidget(svgPicture: state.svgs[index]), - ); - }, + return SizedBox( + width: MediaQuery.of(context).size.width * scalingFactor, + height: MediaQuery.of(context).size.height * scalingFactor, + child: SvgPictureWidget( + svgPicture: state.svgs[index], + ), ); }, ); } + + @override List buildControls( BuildContext context, diff --git a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart index eb23ee264..e0dff486c 100644 --- a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart +++ b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart @@ -344,10 +344,14 @@ class BackButtonWidget extends ConsumerWidget { class SvgPictureWidget extends StatelessWidget { final SvgPicture svgPicture; + final double? width; + final double? height; const SvgPictureWidget({ super.key, required this.svgPicture, + this.width = double.infinity, + this.height = double.infinity, }); @override @@ -358,8 +362,8 @@ class SvgPictureWidget extends StatelessWidget { child: SvgPicture( svgPicture.bytesLoader, fit: BoxFit.contain, - width: double.infinity, - height: double.infinity, + width: width, + height: height, alignment: Alignment.center, ), ); From 6626a199e458f2abd58840a4c03875eeab13057e Mon Sep 17 00:00:00 2001 From: Yassin Date: Sat, 26 Oct 2024 18:15:47 +0300 Subject: [PATCH 21/33] feat: add stop and pause and add close the mode --- .../widget/quran_floating_action_buttons.dart | 55 +++++++++++++++---- .../auto_reading/auto_reading_notifier.dart | 31 +++++++++++ .../auto_reading/auto_reading_state.dart | 7 ++- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 496b9bac4..ae863495f 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -181,22 +181,28 @@ class _PlayPauseButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final autoScrollNotifier = ref.read(autoScrollNotifierProvider.notifier); final autoScrollState = ref.watch(autoScrollNotifierProvider); + return _ActionButton( isPortrait: isPortrait, - icon: autoScrollState.isAutoScrolling ? Icons.pause : Icons.play_arrow, + icon: !autoScrollState.isAutoScrolling ? Icons.play_arrow : autoScrollState.isPlaying ? Icons.pause : Icons.play_arrow, onPressed: () { - final quranReadingStateAsync = ref.read(quranReadingNotifierProvider); - final quranReadingState = quranReadingStateAsync.asData?.value; - - if (quranReadingState != null) { - final currentPage = quranReadingState.currentPage; + if (!autoScrollState.isAutoScrolling) { + final quranReadingState = ref.watch(quranReadingNotifierProvider); + final currentPage = quranReadingState.maybeWhen( + data: (state) => state.currentPage, + orElse: () => 0, + ); final pageHeight = MediaQuery.of(context).size.height; - - ref.read(autoScrollNotifierProvider.notifier).toggleAutoScroll(currentPage, pageHeight); + autoScrollNotifier.toggleAutoScroll(currentPage, pageHeight); + } + if (autoScrollState.isPlaying) { + autoScrollNotifier.pauseAutoScroll(); + } else { + autoScrollNotifier.resumeAutoScroll(); } }, - tooltip: autoScrollState.isAutoScrolling ? 'Pause' : 'Play', focusNode: switchToPlayQuranFocusNode, ); } @@ -219,6 +225,10 @@ class _AutoScrollingReadingMode extends ConsumerWidget { return Column( mainAxisAlignment: MainAxisAlignment.end, children: [ + _ExitButton( + isPortrait: isPortrait, + ), + SizedBox(height: 1.h), _FontSizeControls( isPortrait: isPortrait, fontSize: autoScrollState.fontSize, @@ -260,6 +270,27 @@ class _FontSizeControls extends ConsumerWidget { } } +// Add new Exit button widget +class _ExitButton extends ConsumerWidget { + final bool isPortrait; + + const _ExitButton({ + required this.isPortrait, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return _ActionButton( + isPortrait: isPortrait, + icon: Icons.close, + onPressed: () { + ref.read(autoScrollNotifierProvider.notifier).stopAutoScroll(); + }, + tooltip: 'Exit Auto-Scroll', + ); + } +} + class _SpeedControls extends ConsumerWidget { final QuranReadingState quranReadingState; final bool isPortrait; @@ -279,9 +310,9 @@ class _SpeedControls extends ConsumerWidget { onPressed: () { final pageHeight = MediaQuery.of(context).size.height; ref.read(autoScrollNotifierProvider.notifier).cycleSpeed( - quranReadingState.currentPage, - pageHeight, - ); + quranReadingState.currentPage, + pageHeight, + ); }, tooltip: 'Speed: ${(speed * 100).toInt()}%', ); diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart index f6ae06bea..bec1bd5eb 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart @@ -58,9 +58,23 @@ class AutoScrollNotifier extends AutoDisposeNotifier { + void _startScrolling() { + // Only start scrolling if we're in playing state + if (!state.isPlaying) return; + + state = state.copyWith( + isPlaying: true, + ); + const duration = Duration(milliseconds: 50); _autoScrollTimer = Timer.periodic(duration, (timer) { + // Check if we should be scrolling + if (!state.isPlaying) { + timer.cancel(); + return; + } + if (scrollController.hasClients) { final maxScroll = scrollController.position.maxScrollExtent; final currentScroll = scrollController.offset; @@ -155,6 +169,23 @@ class AutoScrollNotifier extends AutoDisposeNotifier { startAutoScroll(currentPage, pageHeight); } } + + void pauseAutoScroll() { + _autoScrollTimer?.cancel(); + _autoScrollTimer = null; + state = state.copyWith( + isPlaying: false, + ); + } + + void resumeAutoScroll() { + if (!state.isPlaying) { + state = state.copyWith( + isPlaying: true, + ); + _startScrolling(); + } + } } final autoScrollNotifierProvider = AutoDisposeNotifierProvider( diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart index 8ccd209b5..e237e9379 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_state.dart @@ -7,6 +7,7 @@ class AutoScrollState { final double fontSize; final double maxFontSize; final ScrollController scrollController; + final bool isPlaying; AutoScrollState({ required this.scrollController, @@ -15,6 +16,7 @@ class AutoScrollState { this.isVisible = true, this.fontSize = 1.0, this.maxFontSize = 3.0, + this.isPlaying = false, }); bool get isAutoScrolling => isSinglePageView; @@ -28,6 +30,7 @@ class AutoScrollState { double? fontSize, double? maxFontSize, ScrollController? scrollController, + bool? isPlaying, }) { return AutoScrollState( isSinglePageView: isSinglePageView ?? this.isSinglePageView, @@ -36,6 +39,7 @@ class AutoScrollState { fontSize: fontSize ?? this.fontSize, maxFontSize: maxFontSize ?? this.maxFontSize, scrollController: scrollController ?? this.scrollController, + isPlaying: isPlaying ?? this.isPlaying, ); } @@ -48,7 +52,8 @@ class AutoScrollState { 'fontSize: $fontSize, ' 'maxFontSize: $maxFontSize, ' 'isAutoScrolling: $isAutoScrolling, ' - 'showSpeedControl: $showSpeedControl' + 'showSpeedControl: $showSpeedControl,' + 'isPlaying: $isPlaying' ')'; } } From d9ff09472d45fd24e2508f620abf81d5a099e0d2 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sun, 27 Oct 2024 22:34:33 +0300 Subject: [PATCH 22/33] fix: maintain scroll position and speed when changing auto-scroll settings - Prevent scroll position reset when changing scroll speed - Only restart timer instead of full scroll reinitialize when adjusting speed --- .../auto_reading/auto_reading_notifier.dart | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart index bec1bd5eb..a81ca1d32 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart @@ -40,6 +40,13 @@ class AutoScrollNotifier extends AutoDisposeNotifier { Future startAutoScroll(int currentPage, double pageHeight) async { _autoScrollTimer?.cancel(); + + // Store the current scroll position before making changes + double? currentScrollPosition; + if (scrollController.hasClients) { + currentScrollPosition = scrollController.offset; + } + state = state.copyWith( isSinglePageView: true, ); @@ -47,10 +54,15 @@ class AutoScrollNotifier extends AutoDisposeNotifier { // Ensure the ListView is built await Future.delayed(Duration(milliseconds: 50)); - // Jump to the current page + // Restore the previous scroll position if it exists, + // otherwise jump to the current page if (scrollController.hasClients) { - final offset = (currentPage - 1) * pageHeight; - scrollController.jumpTo(offset); + if (currentScrollPosition != null) { + scrollController.jumpTo(currentScrollPosition); + } else { + final offset = (currentPage - 1) * pageHeight; + scrollController.jumpTo(offset); + } } _startScrolling(); @@ -58,7 +70,6 @@ class AutoScrollNotifier extends AutoDisposeNotifier { - void _startScrolling() { // Only start scrolling if we're in playing state if (!state.isPlaying) return; @@ -124,7 +135,7 @@ class AutoScrollNotifier extends AutoDisposeNotifier { if (newSpeed > 5.0) newSpeed = 5.0; state = state.copyWith(autoScrollSpeed: newSpeed); if (state.isAutoScrolling) { - startAutoScroll(currentPage, pageHeight); + _startScrolling(); // Only restart the scrolling timer } } @@ -133,7 +144,7 @@ class AutoScrollNotifier extends AutoDisposeNotifier { if (newSpeed < 0.1) newSpeed = 0.1; state = state.copyWith(autoScrollSpeed: newSpeed); if (state.isAutoScrolling) { - startAutoScroll(currentPage, pageHeight); + _startScrolling(); // Only restart the scrolling timer } } @@ -166,7 +177,7 @@ class AutoScrollNotifier extends AutoDisposeNotifier { } state = state.copyWith(autoScrollSpeed: newSpeed); if (state.isAutoScrolling) { - startAutoScroll(currentPage, pageHeight); + _startScrolling(); // Only restart the scrolling timer } } From ba9b3612ec49541f6ec0f6dc2f48790fcd1dae44 Mon Sep 17 00:00:00 2001 From: Yassin Date: Sun, 27 Oct 2024 22:38:17 +0300 Subject: [PATCH 23/33] remove _handleFloatingActionButtons in the quran floating action --- .../widget/quran_floating_action_buttons.dart | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index ae863495f..df8bce49c 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -82,55 +82,6 @@ class _QuranFloatingActionControlsState extends ConsumerState const Icon(Icons.error), ); } - - KeyEventResult _handleFloatingActionButtons(FocusNode node, event, bool isRotated) { - if (event is KeyDownEvent) { - if (isRotated) { - // Landscape mode navigation - switch (event.logicalKey) { - case LogicalKeyboardKey.arrowUp: - // Navigate between floating action buttons vertically - FocusScope.of(context).previousFocus(); - return KeyEventResult.handled; - - case LogicalKeyboardKey.arrowDown: - // Navigate between floating action buttons vertically - FocusScope.of(context).nextFocus(); - return KeyEventResult.handled; - - case LogicalKeyboardKey.arrowLeft: - // Navigate to left side buttons (Quran switcher, etc.) - if (node == widget.switchToPlayQuranFocusNode) { - FocusScope.of(context).requestFocus(widget.switchQuranModeNode); - } - return KeyEventResult.handled; - - case LogicalKeyboardKey.arrowRight: - // Navigate to right side floating action buttons - if (node == widget.switchQuranModeNode) { - FocusScope.of(context).requestFocus(widget.switchToPlayQuranFocusNode); - } - return KeyEventResult.handled; - } - } else { - if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { - node.focusInDirection(TraversalDirection.left); - return KeyEventResult.handled; - } else if (event.logicalKey == LogicalKeyboardKey.arrowRight) { - ref.read(quranReadingNotifierProvider.notifier).nextPage(); - node.previousFocus(); - return KeyEventResult.handled; - } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - FocusScope.of(context).previousFocus(); - return KeyEventResult.handled; - } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { - FocusScope.of(context).nextFocus(); - return KeyEventResult.handled; - } - } - } - return KeyEventResult.ignored; - } } class _QuranModeButton extends ConsumerWidget { From f5f87164951b5f819271145ca5943a4082a154e0 Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:03:23 +0300 Subject: [PATCH 24/33] feat(quran-reader): Add auto-scroll pause/resume on tap - Add tap gesture detection to auto-scrolling view - Implement play/pause toggle functionality on tap - Disable manual scrolling in auto-scroll mode - Clean up code formatting and indentation --- .../quran/reading/quran_reading_screen.dart | 25 +++++-- .../widget/reading/quran_reading_widgets.dart | 71 ++++++++----------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index d0219734f..4eb66a337 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -64,22 +64,31 @@ class AutoScrollViewStrategy implements QuranViewStrategy { final scalingFactor = autoScrollState.fontSize; return ListView.builder( + physics: NeverScrollableScrollPhysics(), controller: autoScrollState.scrollController, itemCount: state.totalPages, itemBuilder: (context, index) { - return SizedBox( - width: MediaQuery.of(context).size.width * scalingFactor, - height: MediaQuery.of(context).size.height * scalingFactor, - child: SvgPictureWidget( - svgPicture: state.svgs[index], + return GestureDetector( + onTap: () { + final autoScrollNotifier = ref.read(autoScrollNotifierProvider.notifier); + if (autoScrollState.isPlaying) { + autoScrollNotifier.pauseAutoScroll(); + } else { + autoScrollNotifier.resumeAutoScroll(); + } + }, + child: SizedBox( + width: MediaQuery.of(context).size.width * scalingFactor, + height: MediaQuery.of(context).size.height * scalingFactor, + child: SvgPictureWidget( + svgPicture: state.svgs[index], + ), ), ); }, ); } - - @override List buildControls( BuildContext context, @@ -375,7 +384,9 @@ class _QuranReadingScreenState extends ConsumerState { WidgetRef ref, AutoScrollState autoScrollState, ) { + print('used auto scroll view'); return ListView.builder( + physics: NeverScrollableScrollPhysics(), controller: autoScrollState.scrollController, itemCount: quranReadingState.totalPages, itemBuilder: (context, index) { diff --git a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart index e0dff486c..17fc234cb 100644 --- a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart +++ b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart @@ -29,9 +29,7 @@ class VerticalPageViewWidget extends ConsumerWidget { controller: quranReadingState.pageController, onPageChanged: (index) { if (index != quranReadingState.currentPage) { - ref - .read(quranReadingNotifierProvider.notifier) - .updatePage(index, isPortairt: true); + ref.read(quranReadingNotifierProvider.notifier).updatePage(index, isPortairt: true); } }, itemCount: quranReadingState.totalPages, @@ -40,7 +38,6 @@ class VerticalPageViewWidget extends ConsumerWidget { builder: (context, constraints) { final pageWidth = constraints.maxWidth; final pageHeight = constraints.maxHeight; - return Stack( children: [ Positioned.fill( @@ -50,8 +47,7 @@ class VerticalPageViewWidget extends ConsumerWidget { width: pageWidth + 150, height: pageHeight + 100, child: SvgPictureWidget( - svgPicture: quranReadingState - .svgs[index % quranReadingState.svgs.length], + svgPicture: quranReadingState.svgs[index % quranReadingState.svgs.length], ), ), ), @@ -104,8 +100,7 @@ class HorizontalPageViewWidget extends ConsumerWidget { bottom: bottomPadding, width: pageWidth * 0.9, child: SvgPictureWidget( - svgPicture: quranReadingState - .svgs[rightPageIndex % quranReadingState.svgs.length], + svgPicture: quranReadingState.svgs[rightPageIndex % quranReadingState.svgs.length], ), ), if (leftPageIndex < quranReadingState.svgs.length) @@ -115,8 +110,7 @@ class HorizontalPageViewWidget extends ConsumerWidget { bottom: bottomPadding, width: pageWidth * 0.9, child: SvgPictureWidget( - svgPicture: quranReadingState - .svgs[leftPageIndex % quranReadingState.svgs.length], + svgPicture: quranReadingState.svgs[leftPageIndex % quranReadingState.svgs.length], ), ), ], @@ -150,9 +144,7 @@ class RightSwitchButtonWidget extends ConsumerWidget { focusNode: focusNode, opacity: 0.7, iconSize: 14.sp, - icon: Directionality.of(context) == TextDirection.ltr - ? Icons.arrow_forward_ios - : Icons.arrow_back_ios, + icon: Directionality.of(context) == TextDirection.ltr ? Icons.arrow_forward_ios : Icons.arrow_back_ios, onPressed: onPressed, ), ), @@ -182,9 +174,7 @@ class LeftSwitchButtonWidget extends ConsumerWidget { focusNode: focusNode, opacity: 0.7, iconSize: 14.sp, - icon: Directionality.of(context) != TextDirection.ltr - ? Icons.arrow_forward_ios - : Icons.arrow_back_ios, + icon: Directionality.of(context) != TextDirection.ltr ? Icons.arrow_forward_ios : Icons.arrow_back_ios, onPressed: onPressed, ), ), @@ -226,8 +216,7 @@ class PageNumberIndicatorWidget extends ConsumerWidget { ), borderRadius: BorderRadius.circular(20), child: Container( - padding: EdgeInsets.symmetric( - horizontal: 16, vertical: isPortrait ? 8 : 4), + padding: EdgeInsets.symmetric(horizontal: 16, vertical: isPortrait ? 8 : 4), decoration: BoxDecoration( color: Colors.black.withOpacity(0.4), borderRadius: BorderRadius.circular(20), @@ -235,14 +224,14 @@ class PageNumberIndicatorWidget extends ConsumerWidget { child: Text( isPortrait ? S.of(context).quranReadingPagePortrait( - quranReadingState.currentPage + 1, - quranReadingState.totalPages, - ) + quranReadingState.currentPage + 1, + quranReadingState.totalPages, + ) : S.of(context).quranReadingPage( - quranReadingState.currentPage + 1, - quranReadingState.currentPage + 2, - quranReadingState.totalPages, - ), + quranReadingState.currentPage + 1, + quranReadingState.currentPage + 2, + quranReadingState.totalPages, + ), style: TextStyle( color: Colors.white, fontSize: 10.sp, @@ -273,23 +262,23 @@ class MoshafSelectorPositionedWidget extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return isPortrait ? Positioned.directional( - end: 10, - textDirection: Directionality.of(context), - top: 1.h, - child: MoshafSelector( - isAutofocus: !isThereCurrentDialogShowing, - focusNode: focusNode, - ), - ) + end: 10, + textDirection: Directionality.of(context), + top: 1.h, + child: MoshafSelector( + isAutofocus: !isThereCurrentDialogShowing, + focusNode: focusNode, + ), + ) : Positioned( - left: 10, - bottom: 0.5.h, - child: MoshafSelector( - isPortrait: false, - isAutofocus: !isThereCurrentDialogShowing, - focusNode: focusNode, - ), - ); + left: 10, + bottom: 0.5.h, + child: MoshafSelector( + isPortrait: false, + isAutofocus: !isThereCurrentDialogShowing, + focusNode: focusNode, + ), + ); } } From a812d9b8985f533ca64f8fee9f758f6b61b3f3fd Mon Sep 17 00:00:00 2001 From: Yassin Nouh <70436855+YassinNouh21@users.noreply.github.com> Date: Mon, 28 Oct 2024 18:47:55 +0300 Subject: [PATCH 25/33] reformat --- .../quran/page/quran_reading_screen.dart | 1 + .../widget/quran_floating_action_buttons.dart | 6 ++- .../widget/reading/quran_reading_widgets.dart | 46 +++++++++---------- .../auto_reading/auto_reading_notifier.dart | 3 -- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/lib/src/pages/quran/page/quran_reading_screen.dart b/lib/src/pages/quran/page/quran_reading_screen.dart index e69de29bb..8b1378917 100644 --- a/lib/src/pages/quran/page/quran_reading_screen.dart +++ b/lib/src/pages/quran/page/quran_reading_screen.dart @@ -0,0 +1 @@ + diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index df8bce49c..34430f9ec 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -137,7 +137,11 @@ class _PlayPauseButton extends ConsumerWidget { return _ActionButton( isPortrait: isPortrait, - icon: !autoScrollState.isAutoScrolling ? Icons.play_arrow : autoScrollState.isPlaying ? Icons.pause : Icons.play_arrow, + icon: !autoScrollState.isAutoScrolling + ? Icons.play_arrow + : autoScrollState.isPlaying + ? Icons.pause + : Icons.play_arrow, onPressed: () { if (!autoScrollState.isAutoScrolling) { final quranReadingState = ref.watch(quranReadingNotifierProvider); diff --git a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart index 78660aced..17fc234cb 100644 --- a/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart +++ b/lib/src/pages/quran/widget/reading/quran_reading_widgets.dart @@ -224,14 +224,14 @@ class PageNumberIndicatorWidget extends ConsumerWidget { child: Text( isPortrait ? S.of(context).quranReadingPagePortrait( - quranReadingState.currentPage + 1, - quranReadingState.totalPages, - ) + quranReadingState.currentPage + 1, + quranReadingState.totalPages, + ) : S.of(context).quranReadingPage( - quranReadingState.currentPage + 1, - quranReadingState.currentPage + 2, - quranReadingState.totalPages, - ), + quranReadingState.currentPage + 1, + quranReadingState.currentPage + 2, + quranReadingState.totalPages, + ), style: TextStyle( color: Colors.white, fontSize: 10.sp, @@ -262,23 +262,23 @@ class MoshafSelectorPositionedWidget extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return isPortrait ? Positioned.directional( - end: 10, - textDirection: Directionality.of(context), - top: 1.h, - child: MoshafSelector( - isAutofocus: !isThereCurrentDialogShowing, - focusNode: focusNode, - ), - ) + end: 10, + textDirection: Directionality.of(context), + top: 1.h, + child: MoshafSelector( + isAutofocus: !isThereCurrentDialogShowing, + focusNode: focusNode, + ), + ) : Positioned( - left: 10, - bottom: 0.5.h, - child: MoshafSelector( - isPortrait: false, - isAutofocus: !isThereCurrentDialogShowing, - focusNode: focusNode, - ), - ); + left: 10, + bottom: 0.5.h, + child: MoshafSelector( + isPortrait: false, + isAutofocus: !isThereCurrentDialogShowing, + focusNode: focusNode, + ), + ); } } diff --git a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart index a81ca1d32..1ebe00963 100644 --- a/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart +++ b/lib/src/state_management/quran/reading/auto_reading/auto_reading_notifier.dart @@ -29,7 +29,6 @@ class AutoScrollNotifier extends AutoDisposeNotifier { } } - void toggleAutoScroll(int currentPage, double pageHeight) { if (state.isAutoScrolling) { stopAutoScroll(); @@ -68,8 +67,6 @@ class AutoScrollNotifier extends AutoDisposeNotifier { _startScrolling(); } - - void _startScrolling() { // Only start scrolling if we're in playing state if (!state.isPlaying) return; From 263e3a66997386ae0d767726a57c66ff258978cb Mon Sep 17 00:00:00 2001 From: Yassin Date: Tue, 29 Oct 2024 23:05:49 +0300 Subject: [PATCH 26/33] feat(quran): integrate surah name display in SurahSelectorWidget - Replace icon with current surah name display in the top bar - Add transparent background with white text for better visibility - Maintain existing dialog functionality for surah selection --- .../quran/reading/quran_reading_screen.dart | 12 +- .../widget/reading/quran_surah_selector.dart | 223 ++++++++++++------ 2 files changed, 156 insertions(+), 79 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 4eb66a337..6a04f9ba5 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -44,6 +44,7 @@ class FocusNodes { final FocusNode rightSkipNode; final FocusNode pageSelectorNode; final FocusNode switchQuranNode; + final FocusNode surahSelectorNode; FocusNodes({ required this.backButtonNode, @@ -51,6 +52,7 @@ class FocusNodes { required this.rightSkipNode, required this.pageSelectorNode, required this.switchQuranNode, + required this.surahSelectorNode, }); } @@ -147,6 +149,11 @@ class NormalViewStrategy implements QuranViewStrategy { onScroll, isPortrait, ), + SurahSelectorWidget( + isPortrait: isPortrait, + focusNode: focusNodes.surahSelectorNode, + isThereCurrentDialogShowing: false, + ), PageNumberIndicatorWidget( quranReadingState: state, focusNode: focusNodes.pageSelectorNode, @@ -201,6 +208,7 @@ class _QuranReadingScreenState extends ConsumerState { late FocusNode _backButtonFocusNode; late FocusNode _switchQuranFocusNode; late FocusNode _switchQuranModeNode; + late FocusNode _surahSelectorNode; late FocusNode _switchScreenViewFocusNode; late FocusNode _switchToPlayQuranFocusNode; late FocusNode _portraitModeBackButtonFocusNode; @@ -231,6 +239,7 @@ class _QuranReadingScreenState extends ConsumerState { _portraitModeSwitchQuranFocusNode = FocusNode(debugLabel: 'portrait_mode_switch_quran_node'); _portraitModePageSelectorFocusNode = FocusNode(debugLabel: 'portrait_mode_page_selector_node'); _switchToPlayQuranFocusNode = FocusNode(debugLabel: 'switch_to_play_quran_node'); + _surahSelectorNode = FocusNode(debugLabel: 'surah_selector_node'); } @override @@ -249,6 +258,7 @@ class _QuranReadingScreenState extends ConsumerState { _portraitModeSwitchQuranFocusNode.dispose(); _portraitModePageSelectorFocusNode.dispose(); _switchToPlayQuranFocusNode.dispose(); + _surahSelectorNode.dispose(); } @override @@ -339,6 +349,7 @@ class _QuranReadingScreenState extends ConsumerState { rightSkipNode: _rightSkipButtonFocusNode, pageSelectorNode: _portraitModePageSelectorFocusNode, switchQuranNode: _switchQuranFocusNode, + surahSelectorNode: _surahSelectorNode, ); return Stack( @@ -384,7 +395,6 @@ class _QuranReadingScreenState extends ConsumerState { WidgetRef ref, AutoScrollState autoScrollState, ) { - print('used auto scroll view'); return ListView.builder( physics: NeverScrollableScrollPhysics(), controller: autoScrollState.scrollController, diff --git a/lib/src/pages/quran/widget/reading/quran_surah_selector.dart b/lib/src/pages/quran/widget/reading/quran_surah_selector.dart index b6dcb84d4..aee95d62f 100644 --- a/lib/src/pages/quran/widget/reading/quran_surah_selector.dart +++ b/lib/src/pages/quran/widget/reading/quran_surah_selector.dart @@ -1,96 +1,163 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mawaqit/i18n/l10n.dart'; -import 'package:mawaqit/src/state_management/quran/quran/quran_notifier.dart'; import 'package:mawaqit/src/state_management/quran/reading/quran_reading_notifer.dart'; import 'package:sizer/sizer.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; -void showSurahSelector(BuildContext context, int currentPage) { - final AutoScrollController controller = AutoScrollController(); +class SurahSelectorWidget extends ConsumerWidget { + final bool isPortrait; + final FocusNode focusNode; + final bool isThereCurrentDialogShowing; - showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text( - S.of(context).surahSelector, - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.bold, + const SurahSelectorWidget({ + super.key, + required this.isPortrait, + required this.focusNode, + required this.isThereCurrentDialogShowing, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // Don't show the widget in portrait mode + if (isPortrait) { + return const SizedBox.shrink(); + } + + final quranReadingState = ref.watch(quranReadingNotifierProvider); + + return Positioned( + top: 8, + left: 0, + right: 0, + child: Center( + child: quranReadingState.when( + loading: () => const CircularProgressIndicator(), + error: (error, stackTrace) => const SizedBox.shrink(), + data: (state) => Material( + color: Colors.transparent, + child: InkWell( + focusNode: focusNode, + onTap: () { + if (!isThereCurrentDialogShowing) { + ref.read(quranReadingNotifierProvider.notifier).getAllSuwarPage(); + _showSurahSelector(context, ref); + } + }, + borderRadius: BorderRadius.circular(20), + child: Builder( + builder: (context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.4), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + state.currentSurahName, + style: TextStyle( + color: Colors.white, + fontSize: 8.sp, + fontWeight: FontWeight.bold, + ), + ), + ); + }, + ), + ), ), - textAlign: TextAlign.center, ), - content: Container( - width: double.maxFinite, - height: MediaQuery.of(context).size.height * 0.8, - child: Consumer( - builder: (context, ref, _) { - final suwarState = ref.watch(quranReadingNotifierProvider); - return suwarState.when( - loading: () => Center(child: CircularProgressIndicator()), - error: (err, stack) => Center(child: Text('Error: $err')), - data: (quranState) { - final suwar = quranState.suwar; - final currentSurahIndex = suwar.indexWhere((element) => element.name == quranState.currentSurahName); + ), + ); + } - WidgetsBinding.instance.addPostFrameCallback((_) { - controller.scrollToIndex(currentSurahIndex, preferPosition: AutoScrollPosition.begin); - }); + void _showSurahSelector(BuildContext context, WidgetRef ref) { + final AutoScrollController controller = AutoScrollController(); - return GridView.builder( - controller: controller, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - childAspectRatio: 2.5 / 1, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - ), - itemCount: suwar.length, - itemBuilder: (BuildContext context, int index) { - final surah = suwar[index]; - final page = surah.startPage % 2 == 0 ? surah.startPage - 1 : surah.startPage; - return AutoScrollTag( - key: ValueKey(index), - controller: controller, - index: index, - child: InkWell( - autofocus: index == currentSurahIndex, - onTap: () { - ref.read(quranReadingNotifierProvider.notifier).updatePage(page); - Navigator.of(context).pop(); - }, - child: Container( - height: 40.h, - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6.0), - border: Border.all( - color: Theme.of(context).dividerColor, - width: 1, + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + S.of(context).surahSelector, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + content: Container( + width: double.maxFinite, + height: MediaQuery.of(context).size.height * 0.8, + child: Consumer( + builder: (context, ref, _) { + final suwarState = ref.watch(quranReadingNotifierProvider); + return suwarState.when( + loading: () => Center(child: CircularProgressIndicator()), + error: (err, stack) => Center(child: Text('Error: $err')), + data: (quranState) { + final suwar = quranState.suwar; + final currentSurahIndex = + suwar.indexWhere((element) => element.name == quranState.currentSurahName); + + WidgetsBinding.instance.addPostFrameCallback((_) { + controller.scrollToIndex(currentSurahIndex, preferPosition: AutoScrollPosition.begin); + }); + + return GridView.builder( + controller: controller, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 2.5 / 1, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: suwar.length, + itemBuilder: (BuildContext context, int index) { + final surah = suwar[index]; + final page = surah.startPage % 2 == 0 ? surah.startPage - 1 : surah.startPage; + return AutoScrollTag( + key: ValueKey(index), + controller: controller, + index: index, + child: InkWell( + autofocus: index == currentSurahIndex, + onTap: () { + ref.read(quranReadingNotifierProvider.notifier).updatePage(page); + Navigator.of(context).pop(); + }, + child: Container( + height: 40.h, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + border: Border.all( + color: Theme.of(context).dividerColor, + width: 1, + ), ), - ), - child: Text( - "${surah.id}- ${surah.name}", - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 10.sp, - fontWeight: FontWeight.normal, + child: Text( + "${surah.id}- ${surah.name}", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 10.sp, + fontWeight: FontWeight.normal, + ), + overflow: TextOverflow.ellipsis, + maxLines: 2, ), - overflow: TextOverflow.ellipsis, - maxLines: 2, ), ), - ), - ); - }, - ); - }, - ); - }, + ); + }, + ); + }, + ); + }, + ), ), - ), - ); - }, - ); + ); + }, + ); + } } From 8878fe65532c863fe5a8751e0793be8d5556e0de Mon Sep 17 00:00:00 2001 From: Yassin Date: Sun, 3 Nov 2024 22:59:46 +0200 Subject: [PATCH 27/33] feat(ui): show quran reading controls in both portrait & landscape modes - Remove orientation-specific conditional rendering - Display navigation controls, surah selector and page indicators in all orientations - Maintain consistent control behavior across screen modes --- .../quran/reading/quran_reading_screen.dart | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 6a04f9ba5..6f8d8ae8c 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -329,18 +329,18 @@ class _QuranReadingScreenState extends ConsumerState { } Widget _buildBody( - AsyncValue quranReadingState, - bool isPortrait, - UserPreferencesManager userPrefs, - AutoScrollState autoScrollState, - ) { + AsyncValue quranReadingState, + bool isPortrait, + UserPreferencesManager userPrefs, + AutoScrollState autoScrollState, + ) { return quranReadingState.when( loading: () => _buildLoadingIndicator(), error: (error, s) => _buildErrorIndicator(error), data: (state) { // Initialize the appropriate strategy final viewStrategy = - autoScrollState.isSinglePageView ? AutoScrollViewStrategy(autoScrollState) : NormalViewStrategy(isPortrait); + autoScrollState.isSinglePageView ? AutoScrollViewStrategy(autoScrollState) : NormalViewStrategy(isPortrait); // Create focus nodes bundle final focusNodes = FocusNodes( @@ -357,18 +357,16 @@ class _QuranReadingScreenState extends ConsumerState { // Main content viewStrategy.buildView(state, ref, context), - // Controls overlay - if (!isPortrait) ...[ - ...viewStrategy.buildControls( - context, - state, - userPrefs, - isPortrait, - focusNodes, - _scrollPageList, - _showPageSelector, - ), - ], + // Controls overlay - show in both portrait and landscape + ...viewStrategy.buildControls( + context, + state, + userPrefs, + isPortrait, + focusNodes, + _scrollPageList, + _showPageSelector, + ), ], ); }, From 6f6f37c13219e6ce203cc47f387c3c58ff81f88c Mon Sep 17 00:00:00 2001 From: Yassin Date: Mon, 4 Nov 2024 00:04:12 +0200 Subject: [PATCH 28/33] fix: portrait mode focus traversal for Quran reading screen - Removed unused `FocusScopeNode` in `QuranFloatingActionControls`. - Introduced a new focus traversal policy (`PortraitModeFocusTraversalPolicy`) for better keyboard navigation in portrait mode. - Updated `_buildBody` to handle focus nodes in both portrait and landscape orientation --- .../quran/reading/quran_reading_screen.dart | 211 +++++++++++++++++- .../widget/quran_floating_action_buttons.dart | 13 -- 2 files changed, 204 insertions(+), 20 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index 6f8d8ae8c..a8cbd09f4 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -137,6 +137,38 @@ class NormalViewStrategy implements QuranViewStrategy { Function(ScrollDirection, bool) onScroll, Function(BuildContext, int, int, bool) showPageSelector, ) { + if (isPortrait) { + return [ + BackButtonWidget( + isPortrait: isPortrait, + userPrefs: userPrefs, + focusNode: focusNodes.backButtonNode, + ), + _buildNavigationButtons( + context, + focusNodes, + onScroll, + isPortrait, + ), + SurahSelectorWidget( + isPortrait: isPortrait, + focusNode: focusNodes.surahSelectorNode, + isThereCurrentDialogShowing: false, + ), + PageNumberIndicatorWidget( + quranReadingState: state, + focusNode: focusNodes.pageSelectorNode, + isPortrait: isPortrait, + showPageSelector: showPageSelector, + ), + MoshafSelectorPositionedWidget( + isPortrait: isPortrait, + focusNode: focusNodes.switchQuranNode, + isThereCurrentDialogShowing: false, + ), + ]; + } + return [ BackButtonWidget( isPortrait: isPortrait, @@ -329,18 +361,18 @@ class _QuranReadingScreenState extends ConsumerState { } Widget _buildBody( - AsyncValue quranReadingState, - bool isPortrait, - UserPreferencesManager userPrefs, - AutoScrollState autoScrollState, - ) { + AsyncValue quranReadingState, + bool isPortrait, + UserPreferencesManager userPrefs, + AutoScrollState autoScrollState, + ) { return quranReadingState.when( loading: () => _buildLoadingIndicator(), error: (error, s) => _buildErrorIndicator(error), data: (state) { // Initialize the appropriate strategy final viewStrategy = - autoScrollState.isSinglePageView ? AutoScrollViewStrategy(autoScrollState) : NormalViewStrategy(isPortrait); + autoScrollState.isSinglePageView ? AutoScrollViewStrategy(autoScrollState) : NormalViewStrategy(isPortrait); // Create focus nodes bundle final focusNodes = FocusNodes( @@ -351,7 +383,33 @@ class _QuranReadingScreenState extends ConsumerState { switchQuranNode: _switchQuranFocusNode, surahSelectorNode: _surahSelectorNode, ); - + if (isPortrait) { + return FocusTraversalGroup( + policy: PortraitModeFocusTraversalPolicy( + backButtonNode: _backButtonFocusNode, + switchToPlayQuranFocusNode: _switchToPlayQuranFocusNode, + switchQuranNode: _switchQuranFocusNode, + pageSelectorNode: _portraitModePageSelectorFocusNode, + ), + child: Stack( + children: [ + // Main content + viewStrategy.buildView(state, ref, context), + + // Controls overlay - show in both portrait and landscape + ...viewStrategy.buildControls( + context, + state, + userPrefs, + isPortrait, + focusNodes, + _scrollPageList, + _showPageSelector, + ), + ], + ), + ); + } return Stack( children: [ // Main content @@ -535,3 +593,142 @@ class ArrowButtonsFocusTraversalPolicy extends FocusTraversalPolicy { } } } + +class PortraitModeFocusTraversalPolicy extends FocusTraversalPolicy { + final FocusNode backButtonNode; + final FocusNode switchQuranNode; + final FocusNode pageSelectorNode; + final FocusNode switchToPlayQuranFocusNode; + + const PortraitModeFocusTraversalPolicy({ + super.requestFocusCallback, + required this.backButtonNode, + required this.switchQuranNode, + required this.pageSelectorNode, + required this.switchToPlayQuranFocusNode, + }); + + @override + FocusNode? findFirstFocus(FocusNode currentNode, {bool ignoreCurrentFocus = false}) { + return backButtonNode; + } + + @override + FocusNode findLastFocus(FocusNode currentNode, {bool ignoreCurrentFocus = false}) { + return pageSelectorNode; + } + + @override + bool inDirection(FocusNode currentNode, TraversalDirection direction) { + print('Current Node: ${currentNode.debugLabel}, Direction: $direction, || FocusNode: ${currentNode}'); + + if (currentNode == backButtonNode) { + switch (direction) { + case TraversalDirection.right: + if (_canFocusNode(switchQuranNode)) { + _requestFocus(switchQuranNode); + return true; + } + break; + case TraversalDirection.down: + if (_canFocusNode(pageSelectorNode)) { + _requestFocus(pageSelectorNode); + return true; + } + break; + default: + return false; + } + } else if (currentNode == switchQuranNode) { + switch (direction) { + case TraversalDirection.left: + if (_canFocusNode(backButtonNode)) { + _requestFocus(backButtonNode); + return true; + } + break; + case TraversalDirection.right: + if (_canFocusNode(pageSelectorNode)) { + _requestFocus(pageSelectorNode); + return true; + } + break; + case TraversalDirection.down: + if (_canFocusNode(pageSelectorNode)) { + _requestFocus(pageSelectorNode); + return true; + } + break; + default: + break; + } + } else if (currentNode == pageSelectorNode) { + switch (direction) { + case TraversalDirection.up: + if (_canFocusNode(switchQuranNode)) { + _requestFocus(switchQuranNode); + return true; + } + break; + case TraversalDirection.down: + if (_canFocusNode(backButtonNode)) { + _requestFocus(backButtonNode); + return true; + } + break; + case TraversalDirection.left: + if (_canFocusNode(switchQuranNode)) { + _requestFocus(switchQuranNode); + return true; + } + break; + case TraversalDirection.right: + print('Current Node: Right test'); + if (_canFocusNode(switchToPlayQuranFocusNode)) { + _requestFocus(switchToPlayQuranFocusNode); + return true; + } + break; + default: + break; + } + } else if (currentNode == switchToPlayQuranFocusNode) { + switch (direction) { + case TraversalDirection.up: + if (_canFocusNode(switchQuranNode)) { + _requestFocus(switchQuranNode); + return true; + } + break; + case TraversalDirection.left: + if (_canFocusNode(switchQuranNode)) { + _requestFocus(switchQuranNode); + return true; + } + break; + + default: + break; + } + } + return false; + } + + bool _canFocusNode(FocusNode node) { + return node.canRequestFocus; + } + + void _requestFocus(FocusNode node) { + requestFocusCallback.call(node); + } + + @override + FocusNode? findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction) { + return null; + } + + @override + Iterable sortDescendants(Iterable descendants, FocusNode currentNode) { + return [backButtonNode, switchQuranNode, pageSelectorNode].where((node) => descendants.contains(node)); + } +} diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 34430f9ec..a2701d335 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -27,19 +27,6 @@ class QuranFloatingActionControls extends ConsumerStatefulWidget { } class _QuranFloatingActionControlsState extends ConsumerState { - late FocusScopeNode focusScopeNode; - - @override - void initState() { - focusScopeNode = FocusScopeNode(debugLabel: 'quran_floating_action_controls'); - super.initState(); - } - - @override - void dispose() { - focusScopeNode.dispose(); - super.dispose(); - } @override Widget build(BuildContext context) { From 02557bb2fc458032d185b09547ae43acdbab643a Mon Sep 17 00:00:00 2001 From: Yassin Date: Mon, 4 Nov 2024 00:18:50 +0200 Subject: [PATCH 29/33] refactor: `quran_floating_action_buttons.dart` for dynamic button sizing and improved readability - Updated button and icon sizes to scale dynamically based on screen width, enhancing UI consistency across different devices. --- .../quran/reading/quran_reading_screen.dart | 6 - .../widget/quran_floating_action_buttons.dart | 106 +++++++++++++----- 2 files changed, 76 insertions(+), 36 deletions(-) diff --git a/lib/src/pages/quran/reading/quran_reading_screen.dart b/lib/src/pages/quran/reading/quran_reading_screen.dart index a8cbd09f4..a4b764785 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -144,12 +144,6 @@ class NormalViewStrategy implements QuranViewStrategy { userPrefs: userPrefs, focusNode: focusNodes.backButtonNode, ), - _buildNavigationButtons( - context, - focusNodes, - onScroll, - isPortrait, - ), SurahSelectorWidget( isPortrait: isPortrait, focusNode: focusNodes.surahSelectorNode, diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index a2701d335..958f1e73d 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -82,16 +82,28 @@ class _QuranModeButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + // Calculate relative size + double buttonSize = isPortrait + ? MediaQuery + .of(context) + .size + .width * 0.06 // Adjust as needed + : MediaQuery + .of(context) + .size + .width * 0.06; // Adjust as needed + double iconSize = buttonSize * 0.5; // Icon size relative to button size + return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, + width: buttonSize, + height: buttonSize, child: FloatingActionButton( focusNode: switchQuranModeNode, backgroundColor: Colors.black.withOpacity(.3), child: Icon( Icons.headset, color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, + size: iconSize, ), onPressed: () async { ref.read(quranNotifierProvider.notifier).selectModel(QuranMode.listening); @@ -127,8 +139,8 @@ class _PlayPauseButton extends ConsumerWidget { icon: !autoScrollState.isAutoScrolling ? Icons.play_arrow : autoScrollState.isPlaying - ? Icons.pause - : Icons.play_arrow, + ? Icons.pause + : Icons.play_arrow, onPressed: () { if (!autoScrollState.isAutoScrolling) { final quranReadingState = ref.watch(quranReadingNotifierProvider); @@ -136,7 +148,10 @@ class _PlayPauseButton extends ConsumerWidget { data: (state) => state.currentPage, orElse: () => 0, ); - final pageHeight = MediaQuery.of(context).size.height; + final pageHeight = MediaQuery + .of(context) + .size + .height; autoScrollNotifier.toggleAutoScroll(currentPage, pageHeight); } if (autoScrollState.isPlaying) { @@ -250,11 +265,14 @@ class _SpeedControls extends ConsumerWidget { isPortrait: isPortrait, icon: Icons.speed, onPressed: () { - final pageHeight = MediaQuery.of(context).size.height; + final pageHeight = MediaQuery + .of(context) + .size + .height; ref.read(autoScrollNotifierProvider.notifier).cycleSpeed( - quranReadingState.currentPage, - pageHeight, - ); + quranReadingState.currentPage, + pageHeight, + ); }, tooltip: 'Speed: ${(speed * 100).toInt()}%', ); @@ -280,9 +298,22 @@ class _ActionButton extends StatelessWidget { @override Widget build(BuildContext context) { + // Calculate relative size + double buttonSize = isPortrait + ? MediaQuery + .of(context) + .size + .width * 0.06 // Adjust as needed + : MediaQuery + .of(context) + .size + .width * 0.06; // Adjust as needed + double iconSize = buttonSize * 0.5; // Icon size relative to button size + + return SizedBox( - width: isPortrait ? 35.sp : 30.sp, - height: isPortrait ? 35.sp : 30.sp, + width:buttonSize, + height: buttonSize, child: FloatingActionButton( autofocus: autoFocus, focusNode: focusNode, @@ -290,7 +321,7 @@ class _ActionButton extends StatelessWidget { child: Icon( icon, color: Colors.white, - size: isPortrait ? 20.sp : 15.sp, + size: iconSize, ), onPressed: onPressed, heroTag: null, @@ -311,24 +342,39 @@ class _OrientationToggleButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final quranReadingState = ref.watch(quranReadingNotifierProvider); + return quranReadingState.when( - data: (state) => SizedBox( - width: state.isRotated ? 35.sp : 30.sp, - height: state.isRotated ? 35.sp : 30.sp, - child: FloatingActionButton( - focusNode: switchScreenViewFocusNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - !state.isRotated ? Icons.stay_current_portrait : Icons.stay_current_landscape, - color: Colors.white, - size: state.isRotated ? 20.sp : 15.sp, - ), - onPressed: () { - ref.read(quranReadingNotifierProvider.notifier).toggleRotation(); - }, - heroTag: null, - ), - ), + data: (state) { + // Calculate relative size + double buttonSize = state.isRotated + ? MediaQuery + .of(context) + .size + .width * 0.06 // Adjust as needed + : MediaQuery + .of(context) + .size + .width * 0.06; // Adjust as needed + double iconSize = buttonSize * 0.5; // Icon size relative to button size + + return SizedBox( + width: buttonSize, + height: buttonSize, + child: FloatingActionButton( + focusNode: switchScreenViewFocusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + !state.isRotated ? Icons.stay_current_portrait : Icons.stay_current_landscape, + color: Colors.white, + size: iconSize, + ), + onPressed: () { + ref.read(quranReadingNotifierProvider.notifier).toggleRotation(); + }, + heroTag: null, + ), + ); + }, loading: () => const CircularProgressIndicator(), error: (_, __) => const Icon(Icons.error), ); From cca2062ba69d2c89939d4a824cc7c61d75825768 Mon Sep 17 00:00:00 2001 From: Yassin Date: Mon, 4 Nov 2024 00:21:58 +0200 Subject: [PATCH 30/33] refactor --- .../widget/quran_floating_action_buttons.dart | 85 +++++++------------ 1 file changed, 29 insertions(+), 56 deletions(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 958f1e73d..526c813f6 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -27,7 +27,6 @@ class QuranFloatingActionControls extends ConsumerStatefulWidget { } class _QuranFloatingActionControlsState extends ConsumerState { - @override Widget build(BuildContext context) { final quranReadingState = ref.watch(quranReadingNotifierProvider); @@ -84,14 +83,8 @@ class _QuranModeButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { // Calculate relative size double buttonSize = isPortrait - ? MediaQuery - .of(context) - .size - .width * 0.06 // Adjust as needed - : MediaQuery - .of(context) - .size - .width * 0.06; // Adjust as needed + ? MediaQuery.of(context).size.width * 0.06 // Adjust as needed + : MediaQuery.of(context).size.width * 0.06; // Adjust as needed double iconSize = buttonSize * 0.5; // Icon size relative to button size return SizedBox( @@ -139,8 +132,8 @@ class _PlayPauseButton extends ConsumerWidget { icon: !autoScrollState.isAutoScrolling ? Icons.play_arrow : autoScrollState.isPlaying - ? Icons.pause - : Icons.play_arrow, + ? Icons.pause + : Icons.play_arrow, onPressed: () { if (!autoScrollState.isAutoScrolling) { final quranReadingState = ref.watch(quranReadingNotifierProvider); @@ -148,10 +141,7 @@ class _PlayPauseButton extends ConsumerWidget { data: (state) => state.currentPage, orElse: () => 0, ); - final pageHeight = MediaQuery - .of(context) - .size - .height; + final pageHeight = MediaQuery.of(context).size.height; autoScrollNotifier.toggleAutoScroll(currentPage, pageHeight); } if (autoScrollState.isPlaying) { @@ -265,14 +255,11 @@ class _SpeedControls extends ConsumerWidget { isPortrait: isPortrait, icon: Icons.speed, onPressed: () { - final pageHeight = MediaQuery - .of(context) - .size - .height; + final pageHeight = MediaQuery.of(context).size.height; ref.read(autoScrollNotifierProvider.notifier).cycleSpeed( - quranReadingState.currentPage, - pageHeight, - ); + quranReadingState.currentPage, + pageHeight, + ); }, tooltip: 'Speed: ${(speed * 100).toInt()}%', ); @@ -300,19 +287,12 @@ class _ActionButton extends StatelessWidget { Widget build(BuildContext context) { // Calculate relative size double buttonSize = isPortrait - ? MediaQuery - .of(context) - .size - .width * 0.06 // Adjust as needed - : MediaQuery - .of(context) - .size - .width * 0.06; // Adjust as needed + ? MediaQuery.of(context).size.width * 0.06 // Adjust as needed + : MediaQuery.of(context).size.width * 0.06; // Adjust as needed double iconSize = buttonSize * 0.5; // Icon size relative to button size - return SizedBox( - width:buttonSize, + width: buttonSize, height: buttonSize, child: FloatingActionButton( autofocus: autoFocus, @@ -342,38 +322,31 @@ class _OrientationToggleButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final quranReadingState = ref.watch(quranReadingNotifierProvider); - return quranReadingState.when( data: (state) { // Calculate relative size double buttonSize = state.isRotated - ? MediaQuery - .of(context) - .size - .width * 0.06 // Adjust as needed - : MediaQuery - .of(context) - .size - .width * 0.06; // Adjust as needed + ? MediaQuery.of(context).size.width * 0.06 // Adjust as needed + : MediaQuery.of(context).size.width * 0.06; // Adjust as needed double iconSize = buttonSize * 0.5; // Icon size relative to button size return SizedBox( - width: buttonSize, - height: buttonSize, - child: FloatingActionButton( - focusNode: switchScreenViewFocusNode, - backgroundColor: Colors.black.withOpacity(.3), - child: Icon( - !state.isRotated ? Icons.stay_current_portrait : Icons.stay_current_landscape, - color: Colors.white, - size: iconSize, - ), - onPressed: () { - ref.read(quranReadingNotifierProvider.notifier).toggleRotation(); - }, - heroTag: null, + width: buttonSize, + height: buttonSize, + child: FloatingActionButton( + focusNode: switchScreenViewFocusNode, + backgroundColor: Colors.black.withOpacity(.3), + child: Icon( + !state.isRotated ? Icons.stay_current_portrait : Icons.stay_current_landscape, + color: Colors.white, + size: iconSize, ), - ); + onPressed: () { + ref.read(quranReadingNotifierProvider.notifier).toggleRotation(); + }, + heroTag: null, + ), + ); }, loading: () => const CircularProgressIndicator(), error: (_, __) => const Icon(Icons.error), From 46af5b9285c94801da3b186b0c2039a013eaa8d4 Mon Sep 17 00:00:00 2001 From: Yassin Date: Tue, 5 Nov 2024 17:17:23 +0200 Subject: [PATCH 31/33] refactor(quran-reading): update back button behavior and add exit button focus handling - Removed the `BackButtonWidget` from the `quran_reading_screen.dart` page to simplify UI elements. - Enhanced the `_ExitButton` widget in `quran_floating_action_buttons.dart`: - Changed from `ConsumerWidget` to `ConsumerStatefulWidget` for state management. - Added a `FocusNode` for the exit button to set autofocus on load. - Implemented an `initState` method to request focus after widget binding. --- .../quran/reading/quran_reading_screen.dart | 8 +------ .../widget/quran_floating_action_buttons.dart | 24 ++++++++++++++++--- 2 files changed, 22 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 a4b764785..16f8d24b2 100644 --- a/lib/src/pages/quran/reading/quran_reading_screen.dart +++ b/lib/src/pages/quran/reading/quran_reading_screen.dart @@ -101,13 +101,7 @@ class AutoScrollViewStrategy implements QuranViewStrategy { Function(ScrollDirection, bool) onScroll, Function(BuildContext, int, int, bool) showPageSelector, ) { - return [ - BackButtonWidget( - isPortrait: isPortrait, - userPrefs: userPrefs, - focusNode: focusNodes.backButtonNode, - ), - ]; + return []; } } diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 526c813f6..32f4baf10 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -218,17 +218,35 @@ class _FontSizeControls extends ConsumerWidget { } // Add new Exit button widget -class _ExitButton extends ConsumerWidget { +class _ExitButton extends ConsumerStatefulWidget { final bool isPortrait; const _ExitButton({ + super.key, required this.isPortrait, }); @override - Widget build(BuildContext context, WidgetRef ref) { + ConsumerState createState() => __ExitButtonState(); +} + +class __ExitButtonState extends ConsumerState<_ExitButton> { + + late FocusNode exitFocusNode; + @override + void initState() { + exitFocusNode = FocusNode(); + WidgetsBinding.instance.addPostFrameCallback((_) { + exitFocusNode.requestFocus(); + }); + super.initState(); + } + @override + Widget build(BuildContext context) { return _ActionButton( - isPortrait: isPortrait, + autoFocus: true, + focusNode: exitFocusNode, + isPortrait: widget.isPortrait, icon: Icons.close, onPressed: () { ref.read(autoScrollNotifierProvider.notifier).stopAutoScroll(); From 5380759f25eb86a0a556a8448dca5d2035058e56 Mon Sep 17 00:00:00 2001 From: Yassin Date: Tue, 5 Nov 2024 17:22:46 +0200 Subject: [PATCH 32/33] feat: add name for the exitFocusNode --- .../quran/reading/widget/quran_floating_action_buttons.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 32f4baf10..118a85b1e 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -235,7 +235,7 @@ class __ExitButtonState extends ConsumerState<_ExitButton> { late FocusNode exitFocusNode; @override void initState() { - exitFocusNode = FocusNode(); + exitFocusNode = FocusNode(debugLabel: 'exit_focus_node'); WidgetsBinding.instance.addPostFrameCallback((_) { exitFocusNode.requestFocus(); }); From 6ab625bf2895dda8b3ae5deb7e3ecc31c6438efc Mon Sep 17 00:00:00 2001 From: Yassin Date: Tue, 5 Nov 2024 17:24:09 +0200 Subject: [PATCH 33/33] reformat --- .../quran/reading/widget/quran_floating_action_buttons.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart index 118a85b1e..7dd84a18b 100644 --- a/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart +++ b/lib/src/pages/quran/reading/widget/quran_floating_action_buttons.dart @@ -231,7 +231,6 @@ class _ExitButton extends ConsumerStatefulWidget { } class __ExitButtonState extends ConsumerState<_ExitButton> { - late FocusNode exitFocusNode; @override void initState() { @@ -241,6 +240,7 @@ class __ExitButtonState extends ConsumerState<_ExitButton> { }); super.initState(); } + @override Widget build(BuildContext context) { return _ActionButton(