From 7a4c919b22e67913c9a27efe082f198f3ce8ede7 Mon Sep 17 00:00:00 2001 From: DattatreyaReddy Panta <58727124+DattatreyaReddy@users.noreply.github.com> Date: Sat, 21 Oct 2023 12:16:34 +0530 Subject: [PATCH] added reader page change with volume keys --- .../tachidesk_sorayomi/MainActivity.kt | 4 +- lib/src/constants/db_keys.dart | 2 + .../manga_details/manga_details_screen.dart | 10 +- .../presentation/reader/reader_screen.dart | 23 +- .../reader_mode/continuous_reader_mode.dart | 228 +++++++++--------- .../reader_mode/single_page_reader_mode.dart | 5 + .../reader/widgets/reader_wrapper.dart | 92 ++++--- .../reader/reader_settings_screen.dart | 33 ++- .../reader_invert_tap_tile.g.dart | 25 ++ .../reader_volume_tap_invert_tile.dart | 44 ++++ .../reader_volume_tap_invert_tile.g.dart | 26 ++ .../reader_invert_tap_tile.g.dart | 25 ++ .../reader_volume_tap_tile.dart | 44 ++++ .../reader_volume_tap_tile.g.dart | 25 ++ lib/src/l10n/app_en.arb | 12 + pubspec.lock | 8 + pubspec.yaml | 1 + 17 files changed, 440 insertions(+), 167 deletions(-) create mode 100644 lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_invert_tap_tile.g.dart create mode 100644 lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.dart create mode 100644 lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.g.dart create mode 100644 lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_invert_tap_tile.g.dart create mode 100644 lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.dart create mode 100644 lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.g.dart diff --git a/android/app/src/main/kotlin/com/suwayomi/tachidesk_sorayomi/MainActivity.kt b/android/app/src/main/kotlin/com/suwayomi/tachidesk_sorayomi/MainActivity.kt index 40534764..db2eea6c 100644 --- a/android/app/src/main/kotlin/com/suwayomi/tachidesk_sorayomi/MainActivity.kt +++ b/android/app/src/main/kotlin/com/suwayomi/tachidesk_sorayomi/MainActivity.kt @@ -2,5 +2,7 @@ package com.suwayomi.tachidesk_sorayomi import io.flutter.embedding.android.FlutterActivity -class MainActivity: FlutterActivity() { +import dev.darttools.flutter_android_volume_keydown.FlutterAndroidVolumeKeydownActivity; + +class MainActivity: FlutterAndroidVolumeKeydownActivity() { } diff --git a/lib/src/constants/db_keys.dart b/lib/src/constants/db_keys.dart index d27802d3..631b382a 100644 --- a/lib/src/constants/db_keys.dart +++ b/lib/src/constants/db_keys.dart @@ -44,6 +44,8 @@ enum DBKeys { sourceDisplayMode(DisplayMode.grid), gridMangaCoverWidth(192.0), readerOverlay(true), + volumeTap(false), + volumeTapInvert(false), ; const DBKeys(this.initial); diff --git a/lib/src/features/manga_book/presentation/manga_details/manga_details_screen.dart b/lib/src/features/manga_book/presentation/manga_details/manga_details_screen.dart index ec884677..18b967dc 100644 --- a/lib/src/features/manga_book/presentation/manga_details/manga_details_screen.dart +++ b/lib/src/features/manga_book/presentation/manga_details/manga_details_screen.dart @@ -32,12 +32,10 @@ class MangaDetailsScreen extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // Providers as Class for this screen - final mangaProvider = - useMemoized(() => mangaWithIdProvider(mangaId: mangaId), []); - final chapterListProvider = - useMemoized(() => mangaChapterListProvider(mangaId: mangaId), []); - final chapterListFilteredProvider = useMemoized( - () => mangaChapterListWithFilterProvider(mangaId: mangaId), []); + final mangaProvider = mangaWithIdProvider(mangaId: mangaId); + final chapterListProvider = mangaChapterListProvider(mangaId: mangaId); + final chapterListFilteredProvider = + mangaChapterListWithFilterProvider(mangaId: mangaId); final manga = ref.watch(mangaProvider); final filteredChapterList = ref.watch(chapterListFilteredProvider); diff --git a/lib/src/features/manga_book/presentation/reader/reader_screen.dart b/lib/src/features/manga_book/presentation/reader/reader_screen.dart index e9daf7e7..10fa9a2b 100644 --- a/lib/src/features/manga_book/presentation/reader/reader_screen.dart +++ b/lib/src/features/manga_book/presentation/reader/reader_screen.dart @@ -33,18 +33,9 @@ class ReaderScreen extends HookConsumerWidget { final bool showReaderLayoutAnimation; @override Widget build(BuildContext context, WidgetRef ref) { - useEffect(() { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); - return () => SystemChrome.setEnabledSystemUIMode( - SystemUiMode.manual, - overlays: SystemUiOverlay.values, - ); - }, []); - final mangaProvider = - useMemoized(() => mangaWithIdProvider(mangaId: mangaId), []); - final chapterProviderWithIndex = useMemoized( - () => chapterProvider(mangaId: mangaId, chapterIndex: chapterIndex), - []); + final mangaProvider = mangaWithIdProvider(mangaId: mangaId); + final chapterProviderWithIndex = + chapterProvider(mangaId: mangaId, chapterIndex: chapterIndex); final manga = ref.watch(mangaProvider); final chapter = ref.watch(chapterProviderWithIndex); @@ -97,6 +88,14 @@ class ReaderScreen extends HookConsumerWidget { [chapter], ); + useEffect(() { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + return () => SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: SystemUiOverlay.values, + ); + }, []); + return WillPopScope( onWillPop: () async { ref.invalidate(chapterProviderWithIndex); diff --git a/lib/src/features/manga_book/presentation/reader/widgets/reader_mode/continuous_reader_mode.dart b/lib/src/features/manga_book/presentation/reader/widgets/reader_mode/continuous_reader_mode.dart index 06749d57..21ae3842 100644 --- a/lib/src/features/manga_book/presentation/reader/widgets/reader_mode/continuous_reader_mode.dart +++ b/lib/src/features/manga_book/presentation/reader/widgets/reader_mode/continuous_reader_mode.dart @@ -45,6 +45,7 @@ class ContinuousReaderMode extends HookConsumerWidget { final bool showReaderLayoutAnimation; @override Widget build(BuildContext context, WidgetRef ref) { + final touchPoints = useState(0); final scrollController = useMemoized(() => ItemScrollController()); final positionsListener = useMemoized(() => ItemPositionsListener.create()); final currentIndex = useState( @@ -82,119 +83,126 @@ class ContinuousReaderMode extends HookConsumerWidget { final isAnimationEnabled = ref.read(readerScrollAnimationProvider).ifNull(true); return ReaderWrapper( - scrollDirection: scrollDirection, - chapter: chapter, - manga: manga, - showReaderLayoutAnimation: showReaderLayoutAnimation, - currentIndex: currentIndex.value, - onChanged: (index) => scrollController.jumpTo(index: index), - onPrevious: () { - final ItemPosition itemPosition = - positionsListener.itemPositions.value.toList().first; - isAnimationEnabled - ? scrollController.scrollTo( - index: itemPosition.index, - duration: kDuration, - curve: kCurve, - alignment: itemPosition.itemLeadingEdge + .8, - ) - : scrollController.jumpTo( - index: itemPosition.index, - alignment: itemPosition.itemLeadingEdge + .8, - ); - }, - onNext: () { - ItemPosition itemPosition = - positionsListener.itemPositions.value.first; - final int index; - final double alignment; - if (itemPosition.itemTrailingEdge > 1) { - index = itemPosition.index; - alignment = itemPosition.itemLeadingEdge - .8; - } else { - index = itemPosition.index + 1; - alignment = 0; - } - isAnimationEnabled - ? scrollController.scrollTo( - index: index, - duration: kDuration, - curve: kCurve, - alignment: alignment, - ) - : scrollController.jumpTo( - index: index, - alignment: alignment, - ); - }, - child: AppUtils.wrapIf( - !kIsWeb && (Platform.isAndroid || Platform.isIOS) - ? (child) => InteractiveViewer(maxScale: 5, child: child) + touchPoints: touchPoints, + scrollDirection: scrollDirection, + chapter: chapter, + manga: manga, + showReaderLayoutAnimation: showReaderLayoutAnimation, + currentIndex: currentIndex.value, + onChanged: (index) => scrollController.jumpTo(index: index), + onPrevious: () { + final ItemPosition itemPosition = + positionsListener.itemPositions.value.toList().first; + isAnimationEnabled + ? scrollController.scrollTo( + index: itemPosition.index, + duration: kDuration, + curve: kCurve, + alignment: itemPosition.itemLeadingEdge + .8, + ) + : scrollController.jumpTo( + index: itemPosition.index, + alignment: itemPosition.itemLeadingEdge + .8, + ); + }, + onNext: () { + ItemPosition itemPosition = positionsListener.itemPositions.value.first; + final int index; + final double alignment; + if (itemPosition.itemTrailingEdge > 1) { + index = itemPosition.index; + alignment = itemPosition.itemLeadingEdge - .8; + } else { + index = itemPosition.index + 1; + alignment = 0; + } + isAnimationEnabled + ? scrollController.scrollTo( + index: index, + duration: kDuration, + curve: kCurve, + alignment: alignment, + ) + : scrollController.jumpTo( + index: index, + alignment: alignment, + ); + }, + child: AppUtils.wrapIf( + !kIsWeb && (Platform.isAndroid || Platform.isIOS) + ? (child) => InteractiveViewer(maxScale: 5, child: child) + : null, + ScrollablePositionedList.separated( + physics: touchPoints.value >= 2 + ? const NeverScrollableScrollPhysics() : null, - ScrollablePositionedList.separated( - itemScrollController: scrollController, - itemPositionsListener: positionsListener, - initialScrollIndex: chapter.read.ifNull() - ? 0 - : chapter.lastPageRead.getValueOnNullOrNegative(), - scrollDirection: scrollDirection, - reverse: reverse, - itemCount: chapter.pageCount ?? 0, - separatorBuilder: (BuildContext context, int index) => - showSeparator ? KSizedBox.h16.size : const SizedBox.shrink(), - itemBuilder: (BuildContext context, int index) { - final image = ServerImage( - showReloadButton: true, - fit: scrollDirection == Axis.vertical - ? BoxFit.fitWidth - : BoxFit.fitHeight, - appendApiToUrl: true, - imageUrl: MangaUrl.chapterPageWithIndex( - chapterIndex: chapter.index!, - mangaId: manga.id!, - pageIndex: index, + itemScrollController: scrollController, + itemPositionsListener: positionsListener, + initialScrollIndex: chapter.read.ifNull() + ? 0 + : chapter.lastPageRead.getValueOnNullOrNegative(), + scrollDirection: scrollDirection, + reverse: reverse, + itemCount: chapter.pageCount ?? 0, + minCacheExtent: scrollDirection == Axis.vertical + ? context.height * 2 + : context.width * 2, + separatorBuilder: (BuildContext context, int index) => + showSeparator ? KSizedBox.h16.size : const SizedBox.shrink(), + itemBuilder: (BuildContext context, int index) { + final image = ServerImage( + showReloadButton: true, + fit: scrollDirection == Axis.vertical + ? BoxFit.fitWidth + : BoxFit.fitHeight, + appendApiToUrl: true, + imageUrl: MangaUrl.chapterPageWithIndex( + chapterIndex: chapter.index!, + mangaId: manga.id!, + pageIndex: index, + ), + progressIndicatorBuilder: (_, __, downloadProgress) => Center( + child: CircularProgressIndicator( + value: downloadProgress.progress, ), - progressIndicatorBuilder: (_, __, downloadProgress) => Center( - child: CircularProgressIndicator( - value: downloadProgress.progress, - ), - ), - wrapper: (child) => SizedBox( - height: scrollDirection == Axis.vertical - ? context.height * .7 - : null, - width: scrollDirection != Axis.vertical - ? context.width * .7 - : null, - child: child, + ), + wrapper: (child) => SizedBox( + height: scrollDirection == Axis.vertical + ? context.height * .7 + : null, + width: scrollDirection != Axis.vertical + ? context.width * .7 + : null, + child: child, + ), + ); + if (index == 0 || index == (chapter.pageCount ?? 1) - 1) { + final bool reverseDirection = + scrollDirection == Axis.horizontal && reverse; + final separator = SizedBox( + width: scrollDirection != Axis.vertical + ? context.width * .5 + : null, + child: ChapterSeparator( + manga: manga, + chapter: chapter, + isPreviousChapterSeparator: (index == 0), ), ); - if (index == 0 || index == (chapter.pageCount ?? 1) - 1) { - final bool reverseDirection = - scrollDirection == Axis.horizontal && reverse; - final separator = SizedBox( - width: scrollDirection != Axis.vertical - ? context.width * .5 - : null, - child: ChapterSeparator( - manga: manga, - chapter: chapter, - isPreviousChapterSeparator: (index == 0), - ), - ); - return Flex( - direction: scrollDirection, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: ((index == 0) != reverseDirection) - ? [separator, image] - : [image, separator], - ); - } else { - return image; - } - }, - ), - )); + return Flex( + direction: scrollDirection, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: ((index == 0) != reverseDirection) + ? [separator, image] + : [image, separator], + ); + } else { + return image; + } + }, + ), + ), + ); } } diff --git a/lib/src/features/manga_book/presentation/reader/widgets/reader_mode/single_page_reader_mode.dart b/lib/src/features/manga_book/presentation/reader/widgets/reader_mode/single_page_reader_mode.dart index 4176aae6..4944fac4 100644 --- a/lib/src/features/manga_book/presentation/reader/widgets/reader_mode/single_page_reader_mode.dart +++ b/lib/src/features/manga_book/presentation/reader/widgets/reader_mode/single_page_reader_mode.dart @@ -43,6 +43,7 @@ class SinglePageReaderMode extends HookConsumerWidget { final bool showReaderLayoutAnimation; @override Widget build(BuildContext context, WidgetRef ref) { + final touchPoints = useState(0); final cacheManager = useMemoized(() => DefaultCacheManager()); final scrollController = usePageController( initialPage: chapter.read.ifNull() @@ -100,6 +101,7 @@ class SinglePageReaderMode extends HookConsumerWidget { final isAnimationEnabled = ref.read(readerScrollAnimationProvider).ifNull(true); return ReaderWrapper( + touchPoints: touchPoints, scrollDirection: scrollDirection, chapter: chapter, manga: manga, @@ -115,6 +117,9 @@ class SinglePageReaderMode extends HookConsumerWidget { curve: kCurve, ), child: PageView.builder( + physics: touchPoints.value >= 2 + ? const NeverScrollableScrollPhysics() + : null, scrollDirection: scrollDirection, reverse: reverse, controller: scrollController, diff --git a/lib/src/features/manga_book/presentation/reader/widgets/reader_wrapper.dart b/lib/src/features/manga_book/presentation/reader/widgets/reader_wrapper.dart index c12c8dae..316aba5c 100644 --- a/lib/src/features/manga_book/presentation/reader/widgets/reader_wrapper.dart +++ b/lib/src/features/manga_book/presentation/reader/widgets/reader_wrapper.dart @@ -4,8 +4,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_android_volume_keydown/flutter_android_volume_keydown.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -14,7 +17,6 @@ import '../../../../../constants/app_constants.dart'; import '../../../../../constants/app_sizes.dart'; import '../../../../../constants/db_keys.dart'; import '../../../../../constants/enum.dart'; - import '../../../../../constants/reader_keyboard_shortcuts.dart'; import '../../../../../routes/router_config.dart'; import '../../../../../utils/extensions/custom_extensions.dart'; @@ -26,6 +28,8 @@ import '../../../../settings/presentation/reader/widgets/reader_invert_tap_tile/ import '../../../../settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.dart'; import '../../../../settings/presentation/reader/widgets/reader_padding_slider/reader_padding_slider.dart'; import '../../../../settings/presentation/reader/widgets/reader_swipe_toggle_tile/reader_swipe_chapter_toggle_tile.dart'; +import '../../../../settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.dart'; +import '../../../../settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.dart'; import '../../../data/manga_book_repository.dart'; import '../../../domain/chapter/chapter_model.dart'; import '../../../domain/chapter_patch/chapter_put_model.dart'; @@ -47,6 +51,7 @@ class ReaderWrapper extends HookConsumerWidget { required this.onNext, required this.onPrevious, required this.scrollDirection, + required this.touchPoints, this.showReaderLayoutAnimation = false, }); final Widget child; @@ -58,6 +63,7 @@ class ReaderWrapper extends HookConsumerWidget { final int currentIndex; final Axis scrollDirection; final bool showReaderLayoutAnimation; + final ValueNotifier touchPoints; @override Widget build(BuildContext context, WidgetRef ref) { @@ -69,6 +75,9 @@ class ReaderWrapper extends HookConsumerWidget { ); final invertTap = ref.watch(invertTapProvider).ifNull(); + final bool volumeTap = ref.watch(volumeTapProvider).ifNull(); + final bool volumeTapInvert = ref.watch(volumeTapInvertProvider).ifNull(); + final double localMangaReaderPadding = ref.watch(readerPaddingKeyProvider) ?? DBKeys.readerPadding.initial; @@ -148,6 +157,21 @@ class ReaderWrapper extends HookConsumerWidget { return null; }, [visibility.value]); + useEffect(() { + StreamSubscription? subscription; + if (volumeTap) { + subscription = FlutterAndroidVolumeKeydown.stream.listen( + (event) => (switch (event) { + HardwareButton.volume_up => + volumeTapInvert ? onNext() : onPrevious(), + HardwareButton.volume_down => + volumeTapInvert ? onPrevious() : onNext(), + }), + ); + } + return () => subscription?.cancel(); + }, [volumeTap, volumeTapInvert]); + return Theme( data: context.theme.copyWith( bottomSheetTheme: const BottomSheetThemeData( @@ -396,19 +420,28 @@ class ReaderWrapper extends HookConsumerWidget { }, child: Focus( autofocus: true, - child: RepaintBoundary( - child: ReaderView( - toggleVisibility: () => visibility.value = !visibility.value, - scrollDirection: scrollDirection, - mangaReaderPadding: mangaReaderPadding.value, - mangaReaderMagnifierSize: mangaReaderMagnifierSize.value, - onNext: onNext, - onPrevious: onPrevious, - mangaReaderNavigationLayout: mangaReaderNavigationLayout, - prevNextChapterPair: nextPrevChapterPair, - readerSwipeChapterToggle: readerSwipeChapterToggle, - showReaderLayoutAnimation: showReaderLayoutAnimation, - child: child, + child: Listener( + onPointerDown: (_) => touchPoints.value += 1, + onPointerUp: (_) => touchPoints.value -= 1, + onPointerCancel: (_) => touchPoints.value -= 1, + child: RepaintBoundary( + child: ReaderView( + touchPoints: touchPoints, + toggleVisibility: () { + if (touchPoints.value >= 2) return; + visibility.value = !visibility.value; + }, + scrollDirection: scrollDirection, + mangaReaderPadding: mangaReaderPadding.value, + mangaReaderMagnifierSize: mangaReaderMagnifierSize.value, + onNext: onNext, + onPrevious: onPrevious, + mangaReaderNavigationLayout: mangaReaderNavigationLayout, + prevNextChapterPair: nextPrevChapterPair, + readerSwipeChapterToggle: readerSwipeChapterToggle, + showReaderLayoutAnimation: showReaderLayoutAnimation, + child: child, + ), ), ), ), @@ -420,19 +453,21 @@ class ReaderWrapper extends HookConsumerWidget { } class ReaderView extends HookWidget { - const ReaderView( - {super.key, - required this.toggleVisibility, - required this.scrollDirection, - required this.mangaReaderPadding, - required this.mangaReaderMagnifierSize, - required this.onNext, - required this.onPrevious, - required this.prevNextChapterPair, - required this.mangaReaderNavigationLayout, - required this.readerSwipeChapterToggle, - required this.child, - this.showReaderLayoutAnimation = false}); + const ReaderView({ + super.key, + required this.toggleVisibility, + required this.scrollDirection, + required this.mangaReaderPadding, + required this.mangaReaderMagnifierSize, + required this.onNext, + required this.onPrevious, + required this.prevNextChapterPair, + required this.mangaReaderNavigationLayout, + required this.readerSwipeChapterToggle, + required this.child, + required this.touchPoints, + this.showReaderLayoutAnimation = false, + }); final VoidCallback toggleVisibility; final Axis scrollDirection; @@ -445,6 +480,7 @@ class ReaderView extends HookWidget { final bool readerSwipeChapterToggle; final bool showReaderLayoutAnimation; final Widget child; + final ValueNotifier touchPoints; @override Widget build(BuildContext context) { @@ -486,6 +522,7 @@ class ReaderView extends HookWidget { onTap: toggleVisibility, behavior: HitTestBehavior.translucent, onHorizontalDragEnd: (details) { + if (touchPoints.value >= 2) return; if (scrollDirection == Axis.vertical && readerSwipeChapterToggle) { if (details.primaryVelocity == null) { return; @@ -497,6 +534,7 @@ class ReaderView extends HookWidget { } }, onVerticalDragEnd: (details) { + if (touchPoints.value >= 2) return; if (scrollDirection == Axis.horizontal && readerSwipeChapterToggle) { if (details.primaryVelocity == null) { diff --git a/lib/src/features/settings/presentation/reader/reader_settings_screen.dart b/lib/src/features/settings/presentation/reader/reader_settings_screen.dart index c5415a47..efef24af 100644 --- a/lib/src/features/settings/presentation/reader/reader_settings_screen.dart +++ b/lib/src/features/settings/presentation/reader/reader_settings_screen.dart @@ -4,7 +4,11 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +import 'dart:io'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../../utils/extensions/custom_extensions.dart'; import 'widgets/reader_initial_overlay_tile/reader_initial_overlay_tile.dart'; @@ -15,24 +19,31 @@ import 'widgets/reader_navigation_layout_tile/reader_navigation_layout_tile.dart import 'widgets/reader_padding_slider/reader_padding_slider.dart'; import 'widgets/reader_scroll_animation_tile/reader_scroll_animation_tile.dart'; import 'widgets/reader_swipe_toggle_tile/reader_swipe_chapter_toggle_tile.dart'; +import 'widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.dart'; +import 'widgets/reader_volume_tap_tile/reader_volume_tap_tile.dart'; -class ReaderSettingsScreen extends StatelessWidget { +class ReaderSettingsScreen extends ConsumerWidget { const ReaderSettingsScreen({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final isVolumeTapEnabled = ref.watch(volumeTapProvider).ifNull(); return Scaffold( appBar: AppBar(title: Text(context.l10n!.reader)), body: ListView( - children: const [ - ReaderModeTile(), - ReaderNavigationLayoutTile(), - ReaderInvertTapTile(), - ReaderInitialOverlayTile(), - SwipeChapterToggleTile(), - ReaderScrollAnimationTile(), - ReaderPaddingSlider(), - ReaderMagnifierSizeSlider(), + children: [ + const ReaderModeTile(), + const ReaderNavigationLayoutTile(), + const ReaderInvertTapTile(), + const ReaderInitialOverlayTile(), + const SwipeChapterToggleTile(), + const ReaderScrollAnimationTile(), + const ReaderPaddingSlider(), + const ReaderMagnifierSizeSlider(), + if (!kIsWeb && Platform.isAndroid) ...[ + const ReaderVolumeTapTile(), + if (isVolumeTapEnabled) const ReaderVolumeTapInvertTile(), + ] ], ), ); diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_invert_tap_tile.g.dart b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_invert_tap_tile.g.dart new file mode 100644 index 00000000..0b46aa58 --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_invert_tap_tile.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reader_invert_tap_tile.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$invertTapHash() => r'8885890ec1184098ce4769b2488f02078502937b'; + +/// See also [InvertTap]. +@ProviderFor(InvertTap) +final invertTapProvider = + AutoDisposeNotifierProvider.internal( + InvertTap.new, + name: r'invertTapProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$invertTapHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$InvertTap = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.dart b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.dart new file mode 100644 index 00000000..32686e32 --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.dart @@ -0,0 +1,44 @@ +// Copyright (c) 2022 Contributors to the Suwayomi project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +import 'dart:math' as math; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../../../constants/db_keys.dart'; + +import '../../../../../../utils/extensions/custom_extensions.dart'; +import '../../../../../../utils/mixin/shared_preferences_client_mixin.dart'; + +part 'reader_volume_tap_invert_tile.g.dart'; + +@riverpod +class VolumeTapInvert extends _$VolumeTapInvert + with SharedPreferenceClientMixin { + @override + bool? build() => initialize( + ref, + key: DBKeys.volumeTapInvert.name, + initial: DBKeys.volumeTapInvert.initial, + ); +} + +class ReaderVolumeTapInvertTile extends HookConsumerWidget { + const ReaderVolumeTapInvertTile({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + return SwitchListTile( + controlAffinity: ListTileControlAffinity.trailing, + secondary: Transform.rotate( + angle: -math.pi / 2, + child: const Icon(Icons.switch_left_rounded), + ), + title: Text(context.l10n!.readerVolumeTapInvert), + onChanged: ref.read(volumeTapInvertProvider.notifier).update, + value: ref.watch(volumeTapInvertProvider).ifNull(), + ); + } +} diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.g.dart b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.g.dart new file mode 100644 index 00000000..6781b0de --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_invert_tile/reader_volume_tap_invert_tile.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reader_volume_tap_invert_tile.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$volumeTapInvertHash() => r'1d598d8e98e6f885720edc3c3e4d72589efe3880'; + +/// See also [VolumeTapInvert]. +@ProviderFor(VolumeTapInvert) +final volumeTapInvertProvider = + AutoDisposeNotifierProvider.internal( + VolumeTapInvert.new, + name: r'volumeTapInvertProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$volumeTapInvertHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$VolumeTapInvert = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_invert_tap_tile.g.dart b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_invert_tap_tile.g.dart new file mode 100644 index 00000000..0b46aa58 --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_invert_tap_tile.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reader_invert_tap_tile.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$invertTapHash() => r'8885890ec1184098ce4769b2488f02078502937b'; + +/// See also [InvertTap]. +@ProviderFor(InvertTap) +final invertTapProvider = + AutoDisposeNotifierProvider.internal( + InvertTap.new, + name: r'invertTapProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$invertTapHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$InvertTap = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.dart b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.dart new file mode 100644 index 00000000..eb76bc89 --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.dart @@ -0,0 +1,44 @@ +// Copyright (c) 2022 Contributors to the Suwayomi project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +import 'dart:math' as math; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../../../constants/db_keys.dart'; + +import '../../../../../../utils/extensions/custom_extensions.dart'; +import '../../../../../../utils/mixin/shared_preferences_client_mixin.dart'; + +part 'reader_volume_tap_tile.g.dart'; + +@riverpod +class VolumeTap extends _$VolumeTap with SharedPreferenceClientMixin { + @override + bool? build() => initialize( + ref, + key: DBKeys.volumeTap.name, + initial: DBKeys.volumeTap.initial, + ); +} + +class ReaderVolumeTapTile extends HookConsumerWidget { + const ReaderVolumeTapTile({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + return SwitchListTile( + controlAffinity: ListTileControlAffinity.trailing, + secondary: Transform.rotate( + angle: math.pi / 2, + child: const Icon(Icons.switch_left_rounded), + ), + title: Text(context.l10n!.readerVolumeTap), + subtitle: Text(context.l10n!.readerVolumeTapSubtitle), + onChanged: ref.read(volumeTapProvider.notifier).update, + value: ref.watch(volumeTapProvider).ifNull(), + ); + } +} diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.g.dart b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.g.dart new file mode 100644 index 00000000..57697e89 --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_volume_tap_tile/reader_volume_tap_tile.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reader_volume_tap_tile.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$volumeTapHash() => r'7b79b64e431952b2976ee0000cfe14857ada4c30'; + +/// See also [VolumeTap]. +@ProviderFor(VolumeTap) +final volumeTapProvider = + AutoDisposeNotifierProvider.internal( + VolumeTap.new, + name: r'volumeTapProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$volumeTapHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$VolumeTap = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member diff --git a/lib/src/l10n/app_en.arb b/lib/src/l10n/app_en.arb index a5534656..c525d7bf 100644 --- a/lib/src/l10n/app_en.arb +++ b/lib/src/l10n/app_en.arb @@ -502,6 +502,15 @@ "@readerMagnifierSize": { "description": "Slider title text for Reader Magnifier Size" }, + "@readerVolumeTap": { + "description": "Switch button text for Reader Page Navigation with Volume Tap" + }, + "@readerVolumeTapSubtitle": { + "description": "Switch button Subtitle text for Reader Page Navigation with Volume Tap" + }, + "@readerVolumeTapInvert": { + "description": "Switch button text to invert Volume Tap " + }, "@readerPadding": { "description": "Slider title text for Reader Padding" }, @@ -835,6 +844,9 @@ "readerOverlaySubtitle": "Shows title and quick settings when opening a chapter", "readerPadding": "Reader Padding", "readerMagnifierSize": "Magnifier Size", + "readerVolumeTap": "Volume Keys", + "readerVolumeTapSubtitle": "Navigate with Volume Keys", + "readerVolumeTapInvert": "Invert Volume Keys", "reddit": "Reddit", "refresh": "Refresh", "reload": "Reload", diff --git a/pubspec.lock b/pubspec.lock index 5fd48bcf..e52cf19e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -350,6 +350,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_android_volume_keydown: + dependency: "direct main" + description: + name: flutter_android_volume_keydown + sha256: f07fa3e4362262ccf32be6522c8445cbac5a046c18f82c8bd7ba11391d057e9a + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_cache_manager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 232402a4..c8069826 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: file_picker: ^6.0.0 flutter: sdk: flutter + flutter_android_volume_keydown: ^1.0.0 flutter_cache_manager: ^3.3.0 flutter_hooks: ^0.20.0 flutter_localizations: