From 86eeeac66929270c006ecea9aba0ea4ad5cd283a Mon Sep 17 00:00:00 2001 From: DattatreyaReddy Panta <58727124+DattatreyaReddy@users.noreply.github.com> Date: Wed, 8 Mar 2023 02:10:55 +0530 Subject: [PATCH] Added magnification to reader and Fixed category bug (#142) --- lib/src/constants/app_constants.dart | 20 ++- lib/src/constants/app_sizes.dart | 28 ++++ lib/src/constants/db_keys.dart | 1 + .../category/edit_category_screen.dart | 11 +- .../category/widgets/category_tile.dart | 4 +- .../manga_book/domain/manga/manga_model.dart | 6 + .../domain/manga/manga_model.freezed.dart | 45 +++++- .../domain/manga/manga_model.g.dart | 3 + .../widgets/edit_manga_category_dialog.dart | 15 +- .../reader/widgets/reader_wrapper.dart | 141 ++++++++++++++---- .../widgets/grid_cover_min_width.dart | 40 ++--- .../reader/reader_settings_screen.dart | 2 + .../reader_magnifier_size_slider.dart | 95 ++++++++++++ .../reader_magnifier_size_slider.g.dart | 26 ++++ .../reader_padding_slider.dart | 70 +++------ .../slider_setting_tile.dart | 57 +++++++ lib/src/l10n/app_en.arb | 4 + macos/Podfile.lock | 6 +- pubspec.yaml | 2 +- 19 files changed, 445 insertions(+), 131 deletions(-) create mode 100644 lib/src/features/settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.dart create mode 100644 lib/src/features/settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.g.dart create mode 100644 lib/src/features/settings/widgets/slider_setting_tile/slider_setting_tile.dart diff --git a/lib/src/constants/app_constants.dart b/lib/src/constants/app_constants.dart index a79b685e..44e7e579 100644 --- a/lib/src/constants/app_constants.dart +++ b/lib/src/constants/app_constants.dart @@ -6,7 +6,21 @@ import 'package:flutter/material.dart'; -Duration kDuration = const Duration(milliseconds: 500); -Duration kLongDuration = const Duration(seconds: 2); +const Duration kDuration = Duration(milliseconds: 500); +const Duration kLongDuration = Duration(seconds: 2); -Curve kCurve = Curves.easeIn; +const Curve kCurve = Curves.easeIn; +const Size kMagnifierSize = Size(77.37, 37.9); +const MagnifierDecoration kMagnifierDecoration = MagnifierDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(40))), + shadows: [ + BoxShadow( + blurRadius: 1.5, + offset: Offset(0, 2), + spreadRadius: 0.75, + color: Color.fromARGB(25, 0, 0, 0)) + ], +); + +const kDebounceDuration = Duration(milliseconds: 500); diff --git a/lib/src/constants/app_sizes.dart b/lib/src/constants/app_sizes.dart index 8c691c4c..51253479 100644 --- a/lib/src/constants/app_sizes.dart +++ b/lib/src/constants/app_sizes.dart @@ -4,9 +4,12 @@ // 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'; + import 'package:flutter/material.dart'; import '../utils/extensions/custom_extensions.dart'; +import 'app_constants.dart'; import 'db_keys.dart'; const kTabSize = Size.fromHeight(kAppBarBottomHeight); @@ -19,6 +22,31 @@ Size kCalculateAppBarBottomSize(List checks) { return Size.fromHeight(kAppBarBottomHeight * multiplier); } +Offset kMagnifierPosition(Offset position, Size size, double multiplier) => + Offset( + max( + min( + position.dx - (kMagnifierSize.width * multiplier * .5), + size.width - (kMagnifierSize.width * multiplier * .5), + ), + -(kMagnifierSize.width * multiplier * .5), + ), + max( + min( + position.dy - (kMagnifierSize.height * multiplier), + size.height - (kMagnifierSize.height * multiplier * 1.25), + ), + -(kMagnifierSize.height * multiplier * .5), + ), + ); + +Offset kMagnifierOffset(Offset position, Size size, double multiplier) { + return Offset( + 0, + max(0, min(position.dy, (kMagnifierSize.height * multiplier))) * .5, + ); +} + /// Constant sizes to be used in the app (paddings, gaps, rounded corners etc.) enum KEdgeInsets { a8(EdgeInsets.all(8)), diff --git a/lib/src/constants/db_keys.dart b/lib/src/constants/db_keys.dart index 07eda405..fd71a1bf 100644 --- a/lib/src/constants/db_keys.dart +++ b/lib/src/constants/db_keys.dart @@ -18,6 +18,7 @@ enum DBKeys { basicCredentials(null), readerMode(ReaderMode.webtoon), readerPadding(0.0), + readerMagnifierSize(1.0), readerNavigationLayout(ReaderNavigationLayout.disabled), invertTap(false), showNSFW(true), diff --git a/lib/src/features/library/presentation/category/edit_category_screen.dart b/lib/src/features/library/presentation/category/edit_category_screen.dart index 876eb603..b2f6a565 100644 --- a/lib/src/features/library/presentation/category/edit_category_screen.dart +++ b/lib/src/features/library/presentation/category/edit_category_screen.dart @@ -40,7 +40,8 @@ class EditCategoryScreen extends HookConsumerWidget { body: categoryList.showUiWhenData( context, (data) { - if (data.isBlank || data.isSingletonList) { + if (data.isBlank || + (data.isSingletonList && data?.firstOrNull?.id == 0)) { return Emoticons( text: context.l10n!.noCategoriesFound, button: TextButton( @@ -49,9 +50,10 @@ class EditCategoryScreen extends HookConsumerWidget { ), ); } else { + final isDefaultInCategoryList = data!.first.id == 0; return RefreshIndicator( child: ListView.builder( - itemCount: data!.length, + itemCount: data.length, itemBuilder: (context, index) { final category = data[index]; if (category.id == 0) { @@ -59,8 +61,9 @@ class EditCategoryScreen extends HookConsumerWidget { } else { return CategoryTile( key: ValueKey(category.id), - minOrderIndex: 1, - maxOrderIndex: data.length - 1, + maxOrderIndex: isDefaultInCategoryList + ? data.length - 1 + : data.length, category: category, ); } diff --git a/lib/src/features/library/presentation/category/widgets/category_tile.dart b/lib/src/features/library/presentation/category/widgets/category_tile.dart index cfb879bb..1a390d7f 100644 --- a/lib/src/features/library/presentation/category/widgets/category_tile.dart +++ b/lib/src/features/library/presentation/category/widgets/category_tile.dart @@ -20,12 +20,10 @@ class CategoryTile extends HookConsumerWidget { const CategoryTile({ super.key, required this.category, - required this.minOrderIndex, required this.maxOrderIndex, }); final Category category; - final int minOrderIndex; final int maxOrderIndex; @override Widget build(BuildContext context, WidgetRef ref) { @@ -44,7 +42,7 @@ class CategoryTile extends HookConsumerWidget { children: [ IconButton( visualDensity: VisualDensity.compact, - onPressed: order <= minOrderIndex + onPressed: order <= 1 ? null : () => ref .read(categoryControllerProvider.notifier) diff --git a/lib/src/features/manga_book/domain/manga/manga_model.dart b/lib/src/features/manga_book/domain/manga/manga_model.dart index 40d27012..c8b6d34f 100644 --- a/lib/src/features/manga_book/domain/manga/manga_model.dart +++ b/lib/src/features/manga_book/domain/manga/manga_model.dart @@ -63,6 +63,11 @@ class MangaMeta with _$MangaMeta { fromJson: MangaMeta.fromJsonToDouble, ) double? readerPadding, + @JsonKey( + name: "flutter_readerMagnifierSize", + fromJson: MangaMeta.fromJsonToDouble, + ) + double? readerMagnifierSize, }) = _MangaMeta; static bool? fromJsonToBool(dynamic val) => val != null && val is String @@ -80,6 +85,7 @@ enum MangaMetaKeys { readerNavigationLayout("flutter_readerNavigationLayout"), readerMode("flutter_readerMode"), readerPadding("flutter_readerPadding"), + readerMagnifierSize("flutter_readerMagnifierSize"), ; const MangaMetaKeys(this.key); diff --git a/lib/src/features/manga_book/domain/manga/manga_model.freezed.dart b/lib/src/features/manga_book/domain/manga/manga_model.freezed.dart index c35c8622..9a44e15a 100644 --- a/lib/src/features/manga_book/domain/manga/manga_model.freezed.dart +++ b/lib/src/features/manga_book/domain/manga/manga_model.freezed.dart @@ -727,6 +727,9 @@ mixin _$MangaMeta { ReaderMode? get readerMode => throw _privateConstructorUsedError; @JsonKey(name: "flutter_readerPadding", fromJson: MangaMeta.fromJsonToDouble) double? get readerPadding => throw _privateConstructorUsedError; + @JsonKey( + name: "flutter_readerMagnifierSize", fromJson: MangaMeta.fromJsonToDouble) + double? get readerMagnifierSize => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -747,7 +750,9 @@ abstract class $MangaMetaCopyWith<$Res> { @JsonKey(name: "flutter_readerMode") ReaderMode? readerMode, @JsonKey(name: "flutter_readerPadding", fromJson: MangaMeta.fromJsonToDouble) - double? readerPadding}); + double? readerPadding, + @JsonKey(name: "flutter_readerMagnifierSize", fromJson: MangaMeta.fromJsonToDouble) + double? readerMagnifierSize}); } /// @nodoc @@ -767,6 +772,7 @@ class _$MangaMetaCopyWithImpl<$Res, $Val extends MangaMeta> Object? readerNavigationLayout = freezed, Object? readerMode = freezed, Object? readerPadding = freezed, + Object? readerMagnifierSize = freezed, }) { return _then(_value.copyWith( invertTap: freezed == invertTap @@ -785,6 +791,10 @@ class _$MangaMetaCopyWithImpl<$Res, $Val extends MangaMeta> ? _value.readerPadding : readerPadding // ignore: cast_nullable_to_non_nullable as double?, + readerMagnifierSize: freezed == readerMagnifierSize + ? _value.readerMagnifierSize + : readerMagnifierSize // ignore: cast_nullable_to_non_nullable + as double?, ) as $Val); } } @@ -804,7 +814,9 @@ abstract class _$$_MangaMetaCopyWith<$Res> implements $MangaMetaCopyWith<$Res> { @JsonKey(name: "flutter_readerMode") ReaderMode? readerMode, @JsonKey(name: "flutter_readerPadding", fromJson: MangaMeta.fromJsonToDouble) - double? readerPadding}); + double? readerPadding, + @JsonKey(name: "flutter_readerMagnifierSize", fromJson: MangaMeta.fromJsonToDouble) + double? readerMagnifierSize}); } /// @nodoc @@ -822,6 +834,7 @@ class __$$_MangaMetaCopyWithImpl<$Res> Object? readerNavigationLayout = freezed, Object? readerMode = freezed, Object? readerPadding = freezed, + Object? readerMagnifierSize = freezed, }) { return _then(_$_MangaMeta( invertTap: freezed == invertTap @@ -840,6 +853,10 @@ class __$$_MangaMetaCopyWithImpl<$Res> ? _value.readerPadding : readerPadding // ignore: cast_nullable_to_non_nullable as double?, + readerMagnifierSize: freezed == readerMagnifierSize + ? _value.readerMagnifierSize + : readerMagnifierSize // ignore: cast_nullable_to_non_nullable + as double?, )); } } @@ -855,7 +872,9 @@ class _$_MangaMeta implements _MangaMeta { @JsonKey(name: "flutter_readerMode") this.readerMode, @JsonKey(name: "flutter_readerPadding", fromJson: MangaMeta.fromJsonToDouble) - this.readerPadding}); + this.readerPadding, + @JsonKey(name: "flutter_readerMagnifierSize", fromJson: MangaMeta.fromJsonToDouble) + this.readerMagnifierSize}); factory _$_MangaMeta.fromJson(Map json) => _$$_MangaMetaFromJson(json); @@ -874,10 +893,14 @@ class _$_MangaMeta implements _MangaMeta { @override @JsonKey(name: "flutter_readerPadding", fromJson: MangaMeta.fromJsonToDouble) final double? readerPadding; + @override + @JsonKey( + name: "flutter_readerMagnifierSize", fromJson: MangaMeta.fromJsonToDouble) + final double? readerMagnifierSize; @override String toString() { - return 'MangaMeta(invertTap: $invertTap, readerNavigationLayout: $readerNavigationLayout, readerMode: $readerMode, readerPadding: $readerPadding)'; + return 'MangaMeta(invertTap: $invertTap, readerNavigationLayout: $readerNavigationLayout, readerMode: $readerMode, readerPadding: $readerPadding, readerMagnifierSize: $readerMagnifierSize)'; } @override @@ -892,13 +915,15 @@ class _$_MangaMeta implements _MangaMeta { (identical(other.readerMode, readerMode) || other.readerMode == readerMode) && (identical(other.readerPadding, readerPadding) || - other.readerPadding == readerPadding)); + other.readerPadding == readerPadding) && + (identical(other.readerMagnifierSize, readerMagnifierSize) || + other.readerMagnifierSize == readerMagnifierSize)); } @JsonKey(ignore: true) @override int get hashCode => Object.hash(runtimeType, invertTap, - readerNavigationLayout, readerMode, readerPadding); + readerNavigationLayout, readerMode, readerPadding, readerMagnifierSize); @JsonKey(ignore: true) @override @@ -923,7 +948,9 @@ abstract class _MangaMeta implements MangaMeta { @JsonKey(name: "flutter_readerMode") final ReaderMode? readerMode, @JsonKey(name: "flutter_readerPadding", fromJson: MangaMeta.fromJsonToDouble) - final double? readerPadding}) = _$_MangaMeta; + final double? readerPadding, + @JsonKey(name: "flutter_readerMagnifierSize", fromJson: MangaMeta.fromJsonToDouble) + final double? readerMagnifierSize}) = _$_MangaMeta; factory _MangaMeta.fromJson(Map json) = _$_MangaMeta.fromJson; @@ -943,6 +970,10 @@ abstract class _MangaMeta implements MangaMeta { @JsonKey(name: "flutter_readerPadding", fromJson: MangaMeta.fromJsonToDouble) double? get readerPadding; @override + @JsonKey( + name: "flutter_readerMagnifierSize", fromJson: MangaMeta.fromJsonToDouble) + double? get readerMagnifierSize; + @override @JsonKey(ignore: true) _$$_MangaMetaCopyWith<_$_MangaMeta> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/src/features/manga_book/domain/manga/manga_model.g.dart b/lib/src/features/manga_book/domain/manga/manga_model.g.dart index 16060504..677bdf09 100644 --- a/lib/src/features/manga_book/domain/manga/manga_model.g.dart +++ b/lib/src/features/manga_book/domain/manga/manga_model.g.dart @@ -76,6 +76,8 @@ _$_MangaMeta _$$_MangaMetaFromJson(Map json) => _$_MangaMeta( readerMode: $enumDecodeNullable(_$ReaderModeEnumMap, json['flutter_readerMode']), readerPadding: MangaMeta.fromJsonToDouble(json['flutter_readerPadding']), + readerMagnifierSize: + MangaMeta.fromJsonToDouble(json['flutter_readerMagnifierSize']), ); Map _$$_MangaMetaToJson(_$_MangaMeta instance) => @@ -85,6 +87,7 @@ Map _$$_MangaMetaToJson(_$_MangaMeta instance) => _$ReaderNavigationLayoutEnumMap[instance.readerNavigationLayout], 'flutter_readerMode': _$ReaderModeEnumMap[instance.readerMode], 'flutter_readerPadding': instance.readerPadding, + 'flutter_readerMagnifierSize': instance.readerMagnifierSize, }; const _$ReaderNavigationLayoutEnumMap = { diff --git a/lib/src/features/manga_book/presentation/manga_details/widgets/edit_manga_category_dialog.dart b/lib/src/features/manga_book/presentation/manga_details/widgets/edit_manga_category_dialog.dart index 5276e8c3..5d10fb4a 100644 --- a/lib/src/features/manga_book/presentation/manga_details/widgets/edit_manga_category_dialog.dart +++ b/lib/src/features/manga_book/presentation/manga_details/widgets/edit_manga_category_dialog.dart @@ -12,6 +12,7 @@ import '../../../../../constants/app_sizes.dart'; import '../../../../../utils/extensions/custom_extensions.dart'; import '../../../../../widgets/async_buttons/async_checkbox_list_tile.dart'; import '../../../../../widgets/pop_button.dart'; +import '../../../../library/domain/category/category_model.dart'; import '../../../../library/presentation/category/controller/edit_category_controller.dart'; import '../../../data/manga_book_repository.dart'; import '../controller/manga_details_controller.dart'; @@ -58,10 +59,8 @@ class EditMangaCategoryDialog extends HookConsumerWidget { context, (selectedCategoryList) => Column( children: [ - for (int index = 1; index < data!.length; index++) - if (data[index].id == 0) - const SizedBox.shrink() - else + for (Category category in data!) + if (category.id != 0) AsyncCheckboxListTile( onChanged: (value) async { await AsyncValue.guard( @@ -70,22 +69,22 @@ class EditMangaCategoryDialog extends HookConsumerWidget { .read(mangaBookRepositoryProvider) .addMangaToCategory( mangaId, - "${data[index].id!}", + "${category.id!}", ) : ref .read(mangaBookRepositoryProvider) .removeMangaFromCategory( mangaId, - "${data[index].id!}", + "${category.id!}", ), ); await ref.read(provider.notifier).refresh(); }, value: selectedCategoryList?.containsKey( - "${data[index].id}", + "${category.id}", ) ?? false, - title: Text(data[index].name ?? ""), + title: Text(category.name ?? ""), ), ], ), 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 e196fc96..e7c2d023 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 @@ -10,6 +10,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../../../../../constants/app_constants.dart'; import '../../../../../constants/app_sizes.dart'; import '../../../../../constants/db_keys.dart'; import '../../../../../constants/enum.dart'; @@ -19,6 +20,7 @@ import '../../../../../utils/extensions/custom_extensions.dart'; import '../../../../../utils/launch_url_in_web.dart'; import '../../../../../utils/misc/toast/toast.dart'; import '../../../../../widgets/radio_list_popup.dart'; +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 '../../../data/manga_book_repository.dart'; import '../../../domain/chapter/chapter_model.dart'; @@ -62,11 +64,19 @@ class ReaderWrapper extends HookConsumerWidget { chapterIndex: "${chapter.index}", ), ); + final visibility = useState(true); + final double localMangaReaderPadding = ref.watch(readerPaddingKeyProvider) ?? DBKeys.readerPadding.initial; - final visibility = useState(true); final mangaReaderPadding = useState(manga.meta?.readerPadding ?? localMangaReaderPadding); + + final double localMangaReaderMagnifierSize = + ref.watch(readerMagnifierSizeKeyProvider) ?? + DBKeys.readerMagnifierSize.initial; + final mangaReaderMagnifierSize = useState( + manga.meta?.readerMagnifierSize ?? localMangaReaderMagnifierSize); + final mangaReaderMode = manga.meta?.readerMode ?? ReaderMode.defaultReader; final mangaReaderNavigationLayout = manga.meta?.readerNavigationLayout ?? ReaderNavigationLayout.defaultNavigation; @@ -159,6 +169,19 @@ class ReaderWrapper extends HookConsumerWidget { ref.invalidate(mangaWithIdProvider(mangaId: "${manga.id}")); }, ), + AsyncReaderMagnifierSizeSlider( + readerMagnifierSize: mangaReaderMagnifierSize, + onChanged: (value) { + AsyncValue.guard( + () => ref.read(mangaBookRepositoryProvider).patchMangaMeta( + mangaId: "${manga.id}", + key: MangaMetaKeys.readerMagnifierSize.key, + value: value, + ), + ); + ref.invalidate(mangaWithIdProvider(mangaId: "${manga.id}")); + }, + ), ], ); @@ -339,30 +362,16 @@ class ReaderWrapper extends HookConsumerWidget { }, child: Focus( autofocus: true, - child: GestureDetector( - onTap: () => visibility.value = !visibility.value, - behavior: HitTestBehavior.translucent, - child: Stack( - children: [ - Padding( - padding: EdgeInsets.symmetric( - vertical: context.height * - (scrollDirection != Axis.vertical - ? mangaReaderPadding.value - : 0), - horizontal: context.width * - (scrollDirection == Axis.vertical - ? mangaReaderPadding.value - : 0), - ), - child: child, - ), - ReaderNavigationLayoutWidget( - onNext: onNext, - onPrevious: onPrevious, - navigationLayout: mangaReaderNavigationLayout, - ), - ], + child: RepaintBoundary( + child: ReaderView( + toggleVisibility: () => visibility.value = !visibility.value, + scrollDirection: scrollDirection, + mangaReaderPadding: mangaReaderPadding.value, + mangaReaderMagnifierSize: mangaReaderMagnifierSize.value, + onNext: onNext, + onPrevious: onPrevious, + mangaReaderNavigationLayout: mangaReaderNavigationLayout, + child: child, ), ), ), @@ -372,3 +381,85 @@ 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.child, + required this.onNext, + required this.onPrevious, + required this.mangaReaderNavigationLayout, + }); + + final VoidCallback toggleVisibility; + final Axis scrollDirection; + final double mangaReaderPadding; + final double mangaReaderMagnifierSize; + final Widget child; + final VoidCallback onNext; + final VoidCallback onPrevious; + final ReaderNavigationLayout mangaReaderNavigationLayout; + + @override + Widget build(BuildContext context) { + final showMagnification = useState(false); + final dragGesturePosition = useState(Offset.zero); + final positionOffset = kMagnifierPosition( + dragGesturePosition.value, + context.mediaQuerySize, + mangaReaderMagnifierSize, + ); + return Stack( + children: [ + GestureDetector( + onLongPressStart: (details) { + dragGesturePosition.value = details.localPosition; + showMagnification.value = true; + }, + onLongPressEnd: (details) { + dragGesturePosition.value = details.localPosition; + showMagnification.value = false; + }, + onLongPressMoveUpdate: (details) => + dragGesturePosition.value = details.localPosition, + onTap: toggleVisibility, + behavior: HitTestBehavior.translucent, + child: Padding( + padding: EdgeInsets.symmetric( + vertical: context.height * + (scrollDirection != Axis.vertical ? mangaReaderPadding : 0), + horizontal: context.width * + (scrollDirection == Axis.vertical ? mangaReaderPadding : 0), + ), + child: child, + ), + ), + ReaderNavigationLayoutWidget( + onNext: onNext, + onPrevious: onPrevious, + navigationLayout: mangaReaderNavigationLayout, + ), + if (showMagnification.value) + Positioned( + left: positionOffset.dx, + top: positionOffset.dy, + child: RawMagnifier( + decoration: kMagnifierDecoration, + size: kMagnifierSize * mangaReaderMagnifierSize, + focalPointOffset: kMagnifierOffset( + dragGesturePosition.value, + context.mediaQuerySize, + mangaReaderMagnifierSize, + ), + magnificationScale: 2, + child: const ColoredBox(color: Color.fromARGB(8, 158, 158, 158)), + ), + ) + ], + ); + } +} diff --git a/lib/src/features/settings/presentation/appearance/widgets/grid_cover_min_width.dart b/lib/src/features/settings/presentation/appearance/widgets/grid_cover_min_width.dart index 58693929..5e448111 100644 --- a/lib/src/features/settings/presentation/appearance/widgets/grid_cover_min_width.dart +++ b/lib/src/features/settings/presentation/appearance/widgets/grid_cover_min_width.dart @@ -11,6 +11,7 @@ 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'; +import '../../../widgets/slider_setting_tile/slider_setting_tile.dart'; part 'grid_cover_min_width.g.dart'; @@ -30,34 +31,17 @@ class GridCoverMinWidth extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final double width = - ref.watch(gridMinWidthProvider) ?? DBKeys.gridMangaCoverWidth.initial; - return ListTile( - dense: true, - leading: const Icon(Icons.grid_view_rounded), - title: Text(context.l10n!.mangaGridSize), - trailing: IconButton( - onPressed: () => ref - .read(gridMinWidthProvider.notifier) - .update(DBKeys.gridMangaCoverWidth.initial), - icon: const Icon(Icons.refresh), - ), - subtitle: Row( - children: [ - Expanded( - child: Slider( - label: width.toString(), - value: width, - min: 100, - max: 999, - onChanged: (value) => ref - .read(gridMinWidthProvider.notifier) - .update(value.roundToDouble()), - ), - ), - Text(width.round().toString()), - ], - ), + return SliderSettingTile( + defaultValue: DBKeys.gridMangaCoverWidth.initial, + labelGenerator: (value) => value.round().toString(), + title: context.l10n!.mangaGridSize, + icon: Icons.grid_view_rounded, + value: + ref.watch(gridMinWidthProvider) ?? DBKeys.gridMangaCoverWidth.initial, + onChanged: (val) => + ref.read(gridMinWidthProvider.notifier).update(val.roundToDouble()), + min: 100, + max: 999, ); } } 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 9a49f5b1..f757a609 100644 --- a/lib/src/features/settings/presentation/reader/reader_settings_screen.dart +++ b/lib/src/features/settings/presentation/reader/reader_settings_screen.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import '../../../../utils/extensions/custom_extensions.dart'; import 'widgets/reader_invert_tap_tile/reader_invert_tap_tile.dart'; +import 'widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.dart'; import 'widgets/reader_mode_tile/reader_mode_tile.dart'; import 'widgets/reader_navigation_layout_tile/reader_navigation_layout_tile.dart'; import 'widgets/reader_padding_slider/reader_padding_slider.dart'; @@ -25,6 +26,7 @@ class ReaderSettingsScreen extends StatelessWidget { ReaderNavigationLayoutTile(), ReaderInvertTapTile(), ReaderPaddingSlider(), + ReaderMagnifierSizeSlider(), ], ), ); diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.dart b/lib/src/features/settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.dart new file mode 100644 index 00000000..8d2b6b78 --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.dart @@ -0,0 +1,95 @@ +// 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:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../../../../constants/app_constants.dart'; +import '../../../../../../constants/db_keys.dart'; + +import '../../../../../../utils/extensions/custom_extensions.dart'; +import '../../../../../../utils/mixin/shared_preferences_client_mixin.dart'; +import '../../../../widgets/slider_setting_tile/slider_setting_tile.dart'; + +part 'reader_magnifier_size_slider.g.dart'; + +@riverpod +class ReaderMagnifierSizeKey extends _$ReaderMagnifierSizeKey + with SharedPreferenceClientMixin { + @override + double? build() => initialize( + ref, + initial: DBKeys.readerMagnifierSize.initial, + key: DBKeys.readerMagnifierSize.name, + ); +} + +class ReaderMagnifierSizeSlider extends ConsumerWidget { + const ReaderMagnifierSizeSlider({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final double readerMagnifierSize = + ref.watch(readerMagnifierSizeKeyProvider) ?? + DBKeys.readerMagnifierSize.initial; + return SliderSettingTile( + icon: Icons.search, + title: context.l10n!.readerMagnifierSize, + value: readerMagnifierSize, + labelGenerator: (val) => val.toStringAsFixed(2), + onChanged: ref.read(readerMagnifierSizeKeyProvider.notifier).update, + defaultValue: DBKeys.readerMagnifierSize.initial, + min: 1, + max: 5, + ); + } +} + +class AsyncReaderMagnifierSizeSlider extends HookConsumerWidget { + const AsyncReaderMagnifierSizeSlider({ + super.key, + required this.onChanged, + required this.readerMagnifierSize, + }); + + final ValueSetter onChanged; + final ValueNotifier readerMagnifierSize; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final debounce = useRef(null); + + final onDebounceChanged = useCallback>( + (double magnifierSizeValue) async { + readerMagnifierSize.value = magnifierSizeValue; + final finalDebounce = debounce.value; + if ((finalDebounce?.isActive).ifNull()) { + finalDebounce?.cancel(); + } + debounce.value = Timer( + kDebounceDuration, + () => onChanged(magnifierSizeValue), + ); + return; + }, + [], + ); + return SliderSettingTile( + icon: Icons.search, + title: context.l10n!.readerMagnifierSize, + value: readerMagnifierSize.value, + labelGenerator: (val) => val.toStringAsFixed(2), + onChanged: onDebounceChanged, + defaultValue: DBKeys.readerMagnifierSize.initial, + min: 1, + max: 5, + ); + } +} diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.g.dart b/lib/src/features/settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.g.dart new file mode 100644 index 00000000..7499c21e --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_magnifier_size_slider/reader_magnifier_size_slider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reader_magnifier_size_slider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$readerMagnifierSizeKeyHash() => + r'c8e05f6d26e6853042d03b2fff6cc31f7a0950be'; + +/// See also [ReaderMagnifierSizeKey]. +@ProviderFor(ReaderMagnifierSizeKey) +final readerMagnifierSizeKeyProvider = + AutoDisposeNotifierProvider.internal( + ReaderMagnifierSizeKey.new, + name: r'readerMagnifierSizeKeyProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$readerMagnifierSizeKeyHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ReaderMagnifierSizeKey = AutoDisposeNotifier; +// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_padding_slider/reader_padding_slider.dart b/lib/src/features/settings/presentation/reader/widgets/reader_padding_slider/reader_padding_slider.dart index 4e44e1a0..a26de8b7 100644 --- a/lib/src/features/settings/presentation/reader/widgets/reader_padding_slider/reader_padding_slider.dart +++ b/lib/src/features/settings/presentation/reader/widgets/reader_padding_slider/reader_padding_slider.dart @@ -11,10 +11,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../../../../../constants/app_constants.dart'; import '../../../../../../constants/db_keys.dart'; import '../../../../../../utils/extensions/custom_extensions.dart'; import '../../../../../../utils/mixin/shared_preferences_client_mixin.dart'; +import '../../../../widgets/slider_setting_tile/slider_setting_tile.dart'; part 'reader_padding_slider.g.dart'; @@ -36,30 +38,15 @@ class ReaderPaddingSlider extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final double readerPadding = ref.watch(readerPaddingKeyProvider) ?? DBKeys.readerPadding.initial; - return ListTile( - leading: const Icon(Icons.width_wide_rounded), - title: Text(context.l10n!.readerPadding), - trailing: IconButton( - onPressed: () => ref - .read(readerPaddingKeyProvider.notifier) - .update(DBKeys.readerPadding.initial), - icon: const Icon(Icons.refresh), - ), - subtitle: Row( - children: [ - Expanded( - child: Slider( - label: readerPadding.toString(), - value: readerPadding, - min: 0, - max: .4, - onChanged: (value) => - ref.read(readerPaddingKeyProvider.notifier).update(value), - ), - ), - Text((readerPadding * 2.5).toStringAsFixed(2)), - ], - ), + return SliderSettingTile( + icon: Icons.width_wide_rounded, + title: context.l10n!.readerPadding, + value: readerPadding, + labelGenerator: (val) => (val * 2.5).toStringAsFixed(2), + onChanged: ref.read(readerPaddingKeyProvider.notifier).update, + defaultValue: DBKeys.readerPadding.initial, + min: 0, + max: 0.4, ); } } @@ -86,37 +73,22 @@ class AsyncReaderPaddingSlider extends HookConsumerWidget { finalDebounce?.cancel(); } debounce.value = Timer( - const Duration(milliseconds: 500), + kDebounceDuration, () => onChanged(paddingValue), ); return; }, [], ); - - return ListTile( - leading: const Icon(Icons.width_wide_rounded), - title: Text(context.l10n!.readerPadding), - trailing: IconButton( - onPressed: () => onDebounceChanged( - ref.read(readerPaddingKeyProvider) ?? DBKeys.readerPadding.initial, - ), - icon: const Icon(Icons.refresh), - ), - subtitle: Row( - children: [ - Expanded( - child: Slider( - label: readerPadding.value.toString(), - value: readerPadding.value, - min: 0, - max: .4, - onChanged: onDebounceChanged, - ), - ), - Text((readerPadding.value * 2.5).toStringAsFixed(2)), - ], - ), + return SliderSettingTile( + icon: Icons.width_wide_rounded, + title: context.l10n!.readerPadding, + value: readerPadding.value, + labelGenerator: (val) => (val * 2.5).toStringAsFixed(2), + onChanged: onDebounceChanged, + defaultValue: DBKeys.readerPadding.initial, + min: 0, + max: 0.4, ); } } diff --git a/lib/src/features/settings/widgets/slider_setting_tile/slider_setting_tile.dart b/lib/src/features/settings/widgets/slider_setting_tile/slider_setting_tile.dart new file mode 100644 index 00000000..5eb4a168 --- /dev/null +++ b/lib/src/features/settings/widgets/slider_setting_tile/slider_setting_tile.dart @@ -0,0 +1,57 @@ +// Copyright (c) 2023 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 'package:flutter/material.dart'; + +class SliderSettingTile extends StatelessWidget { + const SliderSettingTile({ + super.key, + required this.title, + required this.icon, + required this.value, + required this.defaultValue, + required this.onChanged, + required this.min, + required this.max, + required this.labelGenerator, + }); + final String title; + final IconData icon; + final double value; + final ValueSetter onChanged; + final double min; + final double defaultValue; + final double max; + final String Function(double) labelGenerator; + @override + Widget build(BuildContext context) { + final label = labelGenerator(value); + return ListTile( + dense: false, + isThreeLine: true, + leading: Icon(icon), + title: Text(title), + trailing: IconButton( + onPressed: () => onChanged(defaultValue), + icon: const Icon(Icons.refresh), + ), + subtitle: Row( + children: [ + Expanded( + child: Slider( + label: label.toString(), + value: value, + min: min, + max: max, + onChanged: (value) => onChanged(value), + ), + ), + Text(label), + ], + ), + ); + } +} diff --git a/lib/src/l10n/app_en.arb b/lib/src/l10n/app_en.arb index d89f8974..7867c0f8 100644 --- a/lib/src/l10n/app_en.arb +++ b/lib/src/l10n/app_en.arb @@ -442,6 +442,9 @@ "@readerNavigationLayoutRightAndLeft": { "description": "Radio button text for Reader Navigation Layout - Right And Left" }, + "@readerMagnifierSize": { + "description": "Slider title text for Reader Magnifier Size" + }, "@readerPadding": { "description": "Slider title text for Reader Padding" }, @@ -720,6 +723,7 @@ "readerNavigationLayoutLShaped": "L Shaped", "readerNavigationLayoutRightAndLeft": "Right And Left", "readerPadding": "Reader Padding", + "readerMagnifierSize": "Magnifier Size", "reddit": "Reddit", "refresh": "Refresh", "reset": "Reset", diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 60607f76..ab3d8ee6 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -52,11 +52,11 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 + path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 - shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca + shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472 sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea - url_launcher_macos: c04e4fa86382d4f94f6b38f14625708be3ae52e2 + url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 diff --git a/pubspec.yaml b/pubspec.yaml index 969b02f5..3368561d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: tachidesk_sorayomi description: A new Flutter frontend for Tachidesk. publish_to: "none" -version: 0.4.5+1 +version: 0.4.6+1 environment: sdk: ">=2.18.1 <3.0.0"