diff --git a/lib/src/constants/db_keys.dart b/lib/src/constants/db_keys.dart index 67511c14..d27802d3 100644 --- a/lib/src/constants/db_keys.dart +++ b/lib/src/constants/db_keys.dart @@ -43,6 +43,7 @@ enum DBKeys { libraryDisplayMode(DisplayMode.grid), sourceDisplayMode(DisplayMode.grid), gridMangaCoverWidth(192.0), + readerOverlay(true), ; const DBKeys(this.initial); diff --git a/lib/src/features/browse_center/presentation/source_manga_list/widgets/source_manga_grid_view.dart b/lib/src/features/browse_center/presentation/source_manga_list/widgets/source_manga_grid_view.dart index a24ec59d..a7538521 100644 --- a/lib/src/features/browse_center/presentation/source_manga_list/widgets/source_manga_grid_view.dart +++ b/lib/src/features/browse_center/presentation/source_manga_list/widgets/source_manga_grid_view.dart @@ -12,6 +12,7 @@ import '../../../../../constants/app_sizes.dart'; import '../../../../../routes/router_config.dart'; import '../../../../../utils/extensions/custom_extensions.dart'; +import '../../../../../widgets/custom_circular_progress_indicator.dart'; import '../../../../../widgets/emoticons.dart'; import '../../../../../widgets/manga_cover/grid/manga_cover_grid_tile.dart'; import '../../../../manga_book/domain/manga/manga_model.dart'; @@ -27,6 +28,10 @@ class SourceMangaGridView extends ConsumerWidget { return PagedGridView( pagingController: controller, builderDelegate: PagedChildBuilderDelegate( + firstPageProgressIndicatorBuilder: (context) => + const CenterSorayomiShimmerIndicator(), + newPageProgressIndicatorBuilder: (context) => + const CenterSorayomiShimmerIndicator(), firstPageErrorIndicatorBuilder: (context) => Emoticons( text: controller.error.toString(), button: TextButton( diff --git a/lib/src/features/browse_center/presentation/source_manga_list/widgets/source_manga_list_view.dart b/lib/src/features/browse_center/presentation/source_manga_list/widgets/source_manga_list_view.dart index da7ff228..e91bd9c2 100644 --- a/lib/src/features/browse_center/presentation/source_manga_list/widgets/source_manga_list_view.dart +++ b/lib/src/features/browse_center/presentation/source_manga_list/widgets/source_manga_list_view.dart @@ -6,9 +6,12 @@ import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:shimmer/shimmer.dart'; +import '../../../../../constants/app_sizes.dart'; import '../../../../../routes/router_config.dart'; import '../../../../../utils/extensions/custom_extensions.dart'; +import '../../../../../widgets/custom_circular_progress_indicator.dart'; import '../../../../../widgets/emoticons.dart'; import '../../../../../widgets/manga_cover/list/manga_cover_list_tile.dart'; import '../../../../manga_book/domain/manga/manga_model.dart'; @@ -23,6 +26,36 @@ class SourceMangaListView extends StatelessWidget { return PagedListView( pagingController: controller, builderDelegate: PagedChildBuilderDelegate( + firstPageProgressIndicatorBuilder: (context) => + const CenterSorayomiShimmerIndicator(), + newPageProgressIndicatorBuilder: (context) => Row( + children: [ + SizedBox( + height: 80, + width: 80, + child: ClipRRect( + borderRadius: KBorderRadius.r8.radius, + child: const SorayomiShimmerIndicator(), + ), + ), + Padding( + padding: KEdgeInsets.h8.size, + child: Shimmer.fromColors( + baseColor: context.colorScheme.background, + highlightColor: context.theme.indicatorColor, + child: Container( + width: context.width * .3, + decoration: BoxDecoration( + borderRadius: KBorderRadius.r8.radius, + color: Colors.white, + ), + height: 12, + ), + ), + ), + const Spacer() + ], + ), firstPageErrorIndicatorBuilder: (context) => Emoticons( text: controller.error.toString(), button: TextButton( diff --git a/lib/src/features/manga_book/domain/chapter/chapter_model.dart b/lib/src/features/manga_book/domain/chapter/chapter_model.dart index f93bf307..4a87bae1 100644 --- a/lib/src/features/manga_book/domain/chapter/chapter_model.dart +++ b/lib/src/features/manga_book/domain/chapter/chapter_model.dart @@ -4,6 +4,7 @@ // 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'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../../../utils/extensions/custom_extensions.dart'; @@ -41,4 +42,11 @@ class Chapter with _$Chapter { bool query([String? query]) { return name.query(query) || index == int.tryParse(query ?? ''); } + + String getDisplayName(BuildContext context) { + return name ?? + context.l10n!.chapterNumber( + chapterNumber ?? index?.toDouble() ?? 0, + ); + } } diff --git a/lib/src/features/manga_book/presentation/manga_details/controller/manga_details_controller.dart b/lib/src/features/manga_book/presentation/manga_details/controller/manga_details_controller.dart index 8b2ca822..232cabd1 100644 --- a/lib/src/features/manga_book/presentation/manga_details/controller/manga_details_controller.dart +++ b/lib/src/features/manga_book/presentation/manga_details/controller/manga_details_controller.dart @@ -213,10 +213,11 @@ Chapter? firstUnreadInFilteredChapterList( } @riverpod -({Chapter? first, Chapter? second})? getPreviousAndNextChapters( - GetPreviousAndNextChaptersRef ref, { +({Chapter? first, Chapter? second})? getNextAndPreviousChapters( + GetNextAndPreviousChaptersRef ref, { required int mangaId, required String chapterIndex, + bool shouldAscSort = true, }) { final isAscSorted = ref.watch(mangaChapterSortDirectionProvider) ?? DBKeys.chapterSortDirection.initial; @@ -234,8 +235,8 @@ Chapter? firstUnreadInFilteredChapterList( ? filteredList[currentChapterIndex + 1] : null; return ( - first: isAscSorted ? nextChapter : prevChapter, - second: isAscSorted ? prevChapter : nextChapter, + first: shouldAscSort && isAscSorted ? nextChapter : prevChapter, + second: shouldAscSort && isAscSorted ? prevChapter : nextChapter, ); } } diff --git a/lib/src/features/manga_book/presentation/manga_details/controller/manga_details_controller.g.dart b/lib/src/features/manga_book/presentation/manga_details/controller/manga_details_controller.g.dart index 02b48a45..0d2a9217 100644 --- a/lib/src/features/manga_book/presentation/manga_details/controller/manga_details_controller.g.dart +++ b/lib/src/features/manga_book/presentation/manga_details/controller/manga_details_controller.g.dart @@ -425,37 +425,40 @@ class _FirstUnreadInFilteredChapterListProviderElement (origin as FirstUnreadInFilteredChapterListProvider).mangaId; } -String _$getPreviousAndNextChaptersHash() => - r'72d82e93a696623ac8656cbcc6d2cb999ac58c6a'; +String _$getNextAndPreviousChaptersHash() => + r'c4de0562e24cb2b3a6ead00889b2c5325d8cf7b5'; -/// See also [getPreviousAndNextChapters]. -@ProviderFor(getPreviousAndNextChapters) -const getPreviousAndNextChaptersProvider = GetPreviousAndNextChaptersFamily(); +/// See also [getNextAndPreviousChapters]. +@ProviderFor(getNextAndPreviousChapters) +const getNextAndPreviousChaptersProvider = GetNextAndPreviousChaptersFamily(); -/// See also [getPreviousAndNextChapters]. -class GetPreviousAndNextChaptersFamily +/// See also [getNextAndPreviousChapters]. +class GetNextAndPreviousChaptersFamily extends Family<({Chapter? first, Chapter? second})?> { - /// See also [getPreviousAndNextChapters]. - const GetPreviousAndNextChaptersFamily(); + /// See also [getNextAndPreviousChapters]. + const GetNextAndPreviousChaptersFamily(); - /// See also [getPreviousAndNextChapters]. - GetPreviousAndNextChaptersProvider call({ + /// See also [getNextAndPreviousChapters]. + GetNextAndPreviousChaptersProvider call({ required int mangaId, required String chapterIndex, + bool shouldAscSort = true, }) { - return GetPreviousAndNextChaptersProvider( + return GetNextAndPreviousChaptersProvider( mangaId: mangaId, chapterIndex: chapterIndex, + shouldAscSort: shouldAscSort, ); } @override - GetPreviousAndNextChaptersProvider getProviderOverride( - covariant GetPreviousAndNextChaptersProvider provider, + GetNextAndPreviousChaptersProvider getProviderOverride( + covariant GetNextAndPreviousChaptersProvider provider, ) { return call( mangaId: provider.mangaId, chapterIndex: provider.chapterIndex, + shouldAscSort: provider.shouldAscSort, ); } @@ -471,36 +474,39 @@ class GetPreviousAndNextChaptersFamily _allTransitiveDependencies; @override - String? get name => r'getPreviousAndNextChaptersProvider'; + String? get name => r'getNextAndPreviousChaptersProvider'; } -/// See also [getPreviousAndNextChapters]. -class GetPreviousAndNextChaptersProvider +/// See also [getNextAndPreviousChapters]. +class GetNextAndPreviousChaptersProvider extends AutoDisposeProvider<({Chapter? first, Chapter? second})?> { - /// See also [getPreviousAndNextChapters]. - GetPreviousAndNextChaptersProvider({ + /// See also [getNextAndPreviousChapters]. + GetNextAndPreviousChaptersProvider({ required int mangaId, required String chapterIndex, + bool shouldAscSort = true, }) : this._internal( - (ref) => getPreviousAndNextChapters( - ref as GetPreviousAndNextChaptersRef, + (ref) => getNextAndPreviousChapters( + ref as GetNextAndPreviousChaptersRef, mangaId: mangaId, chapterIndex: chapterIndex, + shouldAscSort: shouldAscSort, ), - from: getPreviousAndNextChaptersProvider, - name: r'getPreviousAndNextChaptersProvider', + from: getNextAndPreviousChaptersProvider, + name: r'getNextAndPreviousChaptersProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$getPreviousAndNextChaptersHash, - dependencies: GetPreviousAndNextChaptersFamily._dependencies, + : _$getNextAndPreviousChaptersHash, + dependencies: GetNextAndPreviousChaptersFamily._dependencies, allTransitiveDependencies: - GetPreviousAndNextChaptersFamily._allTransitiveDependencies, + GetNextAndPreviousChaptersFamily._allTransitiveDependencies, mangaId: mangaId, chapterIndex: chapterIndex, + shouldAscSort: shouldAscSort, ); - GetPreviousAndNextChaptersProvider._internal( + GetNextAndPreviousChaptersProvider._internal( super._createNotifier, { required super.name, required super.dependencies, @@ -509,21 +515,23 @@ class GetPreviousAndNextChaptersProvider required super.from, required this.mangaId, required this.chapterIndex, + required this.shouldAscSort, }) : super.internal(); final int mangaId; final String chapterIndex; + final bool shouldAscSort; @override Override overrideWith( ({Chapter? first, Chapter? second})? Function( - GetPreviousAndNextChaptersRef provider) + GetNextAndPreviousChaptersRef provider) create, ) { return ProviderOverride( origin: this, - override: GetPreviousAndNextChaptersProvider._internal( - (ref) => create(ref as GetPreviousAndNextChaptersRef), + override: GetNextAndPreviousChaptersProvider._internal( + (ref) => create(ref as GetNextAndPreviousChaptersRef), from: from, name: null, dependencies: null, @@ -531,6 +539,7 @@ class GetPreviousAndNextChaptersProvider debugGetCreateSourceHash: null, mangaId: mangaId, chapterIndex: chapterIndex, + shouldAscSort: shouldAscSort, ), ); } @@ -538,14 +547,15 @@ class GetPreviousAndNextChaptersProvider @override AutoDisposeProviderElement<({Chapter? first, Chapter? second})?> createElement() { - return _GetPreviousAndNextChaptersProviderElement(this); + return _GetNextAndPreviousChaptersProviderElement(this); } @override bool operator ==(Object other) { - return other is GetPreviousAndNextChaptersProvider && + return other is GetNextAndPreviousChaptersProvider && other.mangaId == mangaId && - other.chapterIndex == chapterIndex; + other.chapterIndex == chapterIndex && + other.shouldAscSort == shouldAscSort; } @override @@ -553,30 +563,37 @@ class GetPreviousAndNextChaptersProvider var hash = _SystemHash.combine(0, runtimeType.hashCode); hash = _SystemHash.combine(hash, mangaId.hashCode); hash = _SystemHash.combine(hash, chapterIndex.hashCode); + hash = _SystemHash.combine(hash, shouldAscSort.hashCode); return _SystemHash.finish(hash); } } -mixin GetPreviousAndNextChaptersRef +mixin GetNextAndPreviousChaptersRef on AutoDisposeProviderRef<({Chapter? first, Chapter? second})?> { /// The parameter `mangaId` of this provider. int get mangaId; /// The parameter `chapterIndex` of this provider. String get chapterIndex; + + /// The parameter `shouldAscSort` of this provider. + bool get shouldAscSort; } -class _GetPreviousAndNextChaptersProviderElement +class _GetNextAndPreviousChaptersProviderElement extends AutoDisposeProviderElement<({Chapter? first, Chapter? second})?> - with GetPreviousAndNextChaptersRef { - _GetPreviousAndNextChaptersProviderElement(super.provider); + with GetNextAndPreviousChaptersRef { + _GetNextAndPreviousChaptersProviderElement(super.provider); @override - int get mangaId => (origin as GetPreviousAndNextChaptersProvider).mangaId; + int get mangaId => (origin as GetNextAndPreviousChaptersProvider).mangaId; @override String get chapterIndex => - (origin as GetPreviousAndNextChaptersProvider).chapterIndex; + (origin as GetNextAndPreviousChaptersProvider).chapterIndex; + @override + bool get shouldAscSort => + (origin as GetNextAndPreviousChaptersProvider).shouldAscSort; } String _$mangaWithIdHash() => r'aea36ab8126c57f1c73a24b1f8dc6ecbd03f25c1'; 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 94ce4b46..ee8cd880 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 @@ -208,6 +208,7 @@ class MangaDetailsScreen extends HookConsumerWidget { ReaderRoute( mangaId: firstUnreadChapter.mangaId ?? mangaId, chapterIndex: firstUnreadChapter.index ?? 0, + showReaderLayoutAnimation: true, ).push(context); }, ) diff --git a/lib/src/features/manga_book/presentation/manga_details/widgets/chapter_list_tile.dart b/lib/src/features/manga_book/presentation/manga_details/widgets/chapter_list_tile.dart index e2c38f7e..93931df1 100644 --- a/lib/src/features/manga_book/presentation/manga_details/widgets/chapter_list_tile.dart +++ b/lib/src/features/manga_book/presentation/manga_details/widgets/chapter_list_tile.dart @@ -34,6 +34,7 @@ class ChapterListTile extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( + key: Key("manga-${manga.id}-chapter-${chapter.id}"), onSecondaryTap: () => toggleSelect(chapter), child: ListTile( title: Row( @@ -49,8 +50,7 @@ class ChapterListTile extends StatelessWidget { ], Expanded( child: Text( - chapter.name ?? - context.l10n!.chapterNumber(chapter.chapterNumber ?? 0), + chapter.getDisplayName(context), style: TextStyle( color: chapter.read.ifNull() ? Colors.grey : null, ), @@ -105,6 +105,7 @@ class ChapterListTile extends StatelessWidget { : () => ReaderRoute( mangaId: manga.id!, chapterIndex: chapter.index!, + showReaderLayoutAnimation: true, ).push(context), onLongPress: () => toggleSelect(chapter), ), diff --git a/lib/src/features/manga_book/presentation/manga_thumbnail_viewer/manga_thumbnail_viewer.dart b/lib/src/features/manga_book/presentation/manga_thumbnail_viewer/manga_thumbnail_viewer.dart new file mode 100644 index 00000000..f74340fb --- /dev/null +++ b/lib/src/features/manga_book/presentation/manga_thumbnail_viewer/manga_thumbnail_viewer.dart @@ -0,0 +1,43 @@ +// 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 'dart:ui'; + +import 'package:flutter/material.dart'; + +import '../../../../utils/extensions/custom_extensions.dart'; +import '../../../../widgets/server_image.dart'; + +class MangaThumbnailViewer extends StatelessWidget { + const MangaThumbnailViewer({ + super.key, + required this.imageUrl, + }); + final String imageUrl; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + ), + backgroundColor: Colors.transparent, + body: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), + child: Container( + decoration: BoxDecoration( + color: context.colorScheme.background.withOpacity(0.1), + ), + child: InteractiveViewer( + maxScale: 4, + child: Center( + child: ServerImage(imageUrl: imageUrl), + ), + ), + ), + ), + ); + } +} 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 0f292977..efd6f2db 100644 --- a/lib/src/features/manga_book/presentation/reader/reader_screen.dart +++ b/lib/src/features/manga_book/presentation/reader/reader_screen.dart @@ -26,9 +26,11 @@ class ReaderScreen extends HookConsumerWidget { super.key, required this.mangaId, required this.chapterIndex, + this.showReaderLayoutAnimation = false, }); final int mangaId; final int chapterIndex; + final bool showReaderLayoutAnimation; @override Widget build(BuildContext context, WidgetRef ref) { useEffect(() { @@ -116,18 +118,21 @@ class ReaderScreen extends HookConsumerWidget { manga: data, onPageChanged: onPageChanged, scrollDirection: Axis.vertical, + showReaderLayoutAnimation: showReaderLayoutAnimation, ), ReaderMode.singleHorizontalRTL => SinglePageReaderMode( chapter: chapterData, manga: data, onPageChanged: onPageChanged, reverse: true, + showReaderLayoutAnimation: showReaderLayoutAnimation, ), ReaderMode.continuousHorizontalLTR => ContinuousReaderMode( chapter: chapterData, manga: data, onPageChanged: onPageChanged, scrollDirection: Axis.horizontal, + showReaderLayoutAnimation: showReaderLayoutAnimation, ), ReaderMode.continuousHorizontalRTL => ContinuousReaderMode( chapter: chapterData, @@ -135,6 +140,7 @@ class ReaderScreen extends HookConsumerWidget { onPageChanged: onPageChanged, scrollDirection: Axis.horizontal, reverse: true, + showReaderLayoutAnimation: showReaderLayoutAnimation, ), ReaderMode.singleHorizontalLTR => SinglePageReaderMode( chapter: chapterData, @@ -146,16 +152,19 @@ class ReaderScreen extends HookConsumerWidget { manga: data, onPageChanged: onPageChanged, showSeparator: true, + showReaderLayoutAnimation: showReaderLayoutAnimation, ), ReaderMode.webtoon => ContinuousReaderMode( chapter: chapterData, manga: data, onPageChanged: onPageChanged, + showReaderLayoutAnimation: showReaderLayoutAnimation, ), ReaderMode.defaultReader || null => ContinuousReaderMode( chapter: chapterData, manga: data, onPageChanged: onPageChanged, + showReaderLayoutAnimation: showReaderLayoutAnimation, ) }; }, diff --git a/lib/src/features/manga_book/presentation/reader/widgets/chapter_separator.dart b/lib/src/features/manga_book/presentation/reader/widgets/chapter_separator.dart index e6ba191c..c6f7ca36 100644 --- a/lib/src/features/manga_book/presentation/reader/widgets/chapter_separator.dart +++ b/lib/src/features/manga_book/presentation/reader/widgets/chapter_separator.dart @@ -5,16 +5,42 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../../../constants/app_sizes.dart'; +import '../../../../../constants/enum.dart'; +import '../../../../../routes/router_config.dart'; import '../../../../../utils/extensions/custom_extensions.dart'; +import '../../../../settings/presentation/reader/widgets/reader_navigation_layout_tile/reader_navigation_layout_tile.dart'; +import '../../../domain/chapter/chapter_model.dart'; +import '../../../domain/manga/manga_model.dart'; +import '../../manga_details/controller/manga_details_controller.dart'; -class ChapterSeparator extends StatelessWidget { - const ChapterSeparator({super.key, required this.title, required this.name}); - final String title; - final String name; +class ChapterSeparator extends ConsumerWidget { + const ChapterSeparator({ + super.key, + required this.manga, + required this.chapter, + required this.isPreviousChapterSeparator, + }); + final Manga manga; + final Chapter chapter; + final bool isPreviousChapterSeparator; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final nextPrevChapterPair = ref.watch( + getNextAndPreviousChaptersProvider( + mangaId: manga.id!, + chapterIndex: "${chapter.index}", + ), + ); + final navigationLayout = ref.watch(readerNavigationLayoutKeyProvider); + final showPrevNextButtons = + manga.meta?.readerNavigationLayout == ReaderNavigationLayout.disabled || + ((manga.meta?.readerNavigationLayout == null || + manga.meta?.readerNavigationLayout == + ReaderNavigationLayout.defaultNavigation) && + navigationLayout == ReaderNavigationLayout.disabled); return Center( child: SingleChildScrollView( child: Column( @@ -22,18 +48,55 @@ class ChapterSeparator extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ KSizedBox.h16.size, + if (showPrevNextButtons && + nextPrevChapterPair?.second != null && + isPreviousChapterSeparator) + Padding( + padding: KEdgeInsets.v16.size, + child: FilledButton( + onPressed: () => ReaderRoute( + mangaId: nextPrevChapterPair!.second!.mangaId!, + chapterIndex: nextPrevChapterPair.second!.index!, + ).pushReplacement(context), + child: Text( + context.l10n!.previousChapter( + nextPrevChapterPair?.second?.getDisplayName(context) ?? + "", + ), + ), + ), + ), Text( - "$title:", + isPreviousChapterSeparator + ? context.l10n!.start + : context.l10n!.finished, style: context.textTheme.titleMedium ?.copyWith(fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis, ), Text( - name, + chapter.getDisplayName(context), style: context.textTheme.bodyMedium ?.copyWith(fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis, ), + if (showPrevNextButtons && + nextPrevChapterPair?.first != null && + !isPreviousChapterSeparator) + Padding( + padding: KEdgeInsets.v16.size, + child: FilledButton( + onPressed: () => ReaderRoute( + mangaId: nextPrevChapterPair!.first!.mangaId!, + chapterIndex: nextPrevChapterPair.first!.index!, + ).pushReplacement(context), + child: Text( + context.l10n!.nextChapter( + nextPrevChapterPair?.first?.getDisplayName(context) ?? "", + ), + ), + ), + ), KSizedBox.h16.size, ], ), 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 ffe6dbda..0ac9deb5 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 @@ -4,6 +4,8 @@ // 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/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -14,6 +16,7 @@ import '../../../../../../constants/app_sizes.dart'; import '../../../../../../constants/endpoints.dart'; import '../../../../../../utils/extensions/custom_extensions.dart'; +import '../../../../../../utils/misc/app_utils.dart'; import '../../../../../../widgets/server_image.dart'; import '../../../../../settings/presentation/reader/widgets/reader_scroll_animation_tile/reader_scroll_animation_tile.dart'; import '../../../../domain/chapter/chapter_model.dart'; @@ -30,6 +33,7 @@ class ContinuousReaderMode extends HookConsumerWidget { this.onPageChanged, this.scrollDirection = Axis.vertical, this.reverse = false, + this.showReaderLayoutAnimation = false, }); final Manga manga; final Chapter chapter; @@ -37,6 +41,7 @@ class ContinuousReaderMode extends HookConsumerWidget { final ValueSetter? onPageChanged; final Axis scrollDirection; final bool reverse; + final bool showReaderLayoutAnimation; @override Widget build(BuildContext context, WidgetRef ref) { final scrollController = useMemoized(() => ItemScrollController()); @@ -74,111 +79,119 @@ class ContinuousReaderMode extends HookConsumerWidget { final isAnimationEnabled = ref.read(readerScrollAnimationProvider).ifNull(true); return ReaderWrapper( - scrollDirection: scrollDirection, - chapter: chapter, - manga: manga, - 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: 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, - ), - 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, - ), - ); - if (index == 0 || index == (chapter.pageCount ?? 1) - 1) { - final separator = SizedBox( - width: - scrollDirection != Axis.vertical ? context.width * .5 : null, - child: ChapterSeparator( - title: - index == 0 ? context.l10n!.current : context.l10n!.finished, - name: chapter.name ?? - context.l10n!.chapterNumber(chapter.chapterNumber ?? 0), - ), - ); - final bool reverseDirection = - scrollDirection == Axis.horizontal && reverse; - return Flex( - direction: scrollDirection, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: ((index == 0) != reverseDirection) - ? [separator, image] - : [image, separator], - ); + 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 { - return image; + 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( + Platform.isAndroid || Platform.isIOS + ? (child) => InteractiveViewer(maxScale: 5, child: child) + : 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, + ), + 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, + ), + ); + 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; + } + }, + ), + )); } } 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 6ff98957..a79c1fa4 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 @@ -4,6 +4,8 @@ // 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/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -13,6 +15,7 @@ import '../../../../../../constants/app_constants.dart'; import '../../../../../../constants/endpoints.dart'; import '../../../../../../utils/extensions/cache_manager_extensions.dart'; import '../../../../../../utils/extensions/custom_extensions.dart'; +import '../../../../../../utils/misc/app_utils.dart'; import '../../../../../../widgets/custom_circular_progress_indicator.dart'; import '../../../../../../widgets/server_image.dart'; import '../../../../../settings/presentation/reader/widgets/reader_scroll_animation_tile/reader_scroll_animation_tile.dart'; @@ -28,6 +31,7 @@ class SinglePageReaderMode extends HookConsumerWidget { this.onPageChanged, this.reverse = false, this.scrollDirection = Axis.horizontal, + this.showReaderLayoutAnimation = false, }); final Manga manga; @@ -35,6 +39,7 @@ class SinglePageReaderMode extends HookConsumerWidget { final ValueSetter? onPageChanged; final bool reverse; final Axis scrollDirection; + final bool showReaderLayoutAnimation; @override Widget build(BuildContext context, WidgetRef ref) { final cacheManager = useMemoized(() => DefaultCacheManager()); @@ -99,6 +104,7 @@ class SinglePageReaderMode extends HookConsumerWidget { manga: manga, currentIndex: currentIndex.value, onChanged: (index) => scrollController.jumpToPage(index), + showReaderLayoutAnimation: showReaderLayoutAnimation, onPrevious: () => scrollController.previousPage( duration: isAnimationEnabled ? kDuration : kInstantDuration, curve: kCurve, @@ -123,11 +129,16 @@ class SinglePageReaderMode extends HookConsumerWidget { pageIndex: index, ), progressIndicatorBuilder: (context, url, downloadProgress) => - CenterCircularProgressIndicator( + CenterSorayomiShimmerIndicator( value: downloadProgress.progress, ), ); - return image; + return AppUtils.wrapIf( + Platform.isAndroid || Platform.isIOS + ? (child) => InteractiveViewer(maxScale: 5, child: child) + : null, + image, + ); }, itemCount: chapter.pageCount.getValueOnNullOrNegative(), ), diff --git a/lib/src/features/manga_book/presentation/reader/widgets/reader_navigation_layout/reader_navigation_layout.dart b/lib/src/features/manga_book/presentation/reader/widgets/reader_navigation_layout/reader_navigation_layout.dart index a4b73bf6..73894c96 100644 --- a/lib/src/features/manga_book/presentation/reader/widgets/reader_navigation_layout/reader_navigation_layout.dart +++ b/lib/src/features/manga_book/presentation/reader/widgets/reader_navigation_layout/reader_navigation_layout.dart @@ -24,19 +24,23 @@ class ReaderNavigationLayoutWidget extends HookConsumerWidget { this.navigationLayout, required this.onPrevious, required this.onNext, + this.showReaderLayoutAnimation = false, }); final ReaderNavigationLayout? navigationLayout; final VoidCallback? onPrevious; final VoidCallback? onNext; + final bool showReaderLayoutAnimation; @override Widget build(BuildContext context, WidgetRef ref) { final animationController = useAnimationController(duration: kLongDuration); useAnimation(animationController); - final nextColorTween = - ColorTween(begin: Colors.green).animate(animationController).value; + final nextColorTween = ColorTween( + begin: showReaderLayoutAnimation ? Colors.green : Colors.transparent, + ).animate(animationController).value; - final prevColorTween = - ColorTween(begin: Colors.blue).animate(animationController).value; + final prevColorTween = ColorTween( + begin: showReaderLayoutAnimation ? Colors.blue : Colors.transparent, + ).animate(animationController).value; useEffect(() { animationController.forward(); return; 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 4ce4cbc4..ba1703e1 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 @@ -21,6 +21,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_initial_overlay_tile/reader_initial_overlay_tile.dart'; import '../../../../settings/presentation/reader/widgets/reader_invert_tap_tile/reader_invert_tap_tile.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'; @@ -46,6 +47,7 @@ class ReaderWrapper extends HookConsumerWidget { required this.onNext, required this.onPrevious, required this.scrollDirection, + this.showReaderLayoutAnimation = false, }); final Widget child; final Manga manga; @@ -55,11 +57,12 @@ class ReaderWrapper extends HookConsumerWidget { final VoidCallback onNext; final int currentIndex; final Axis scrollDirection; + final bool showReaderLayoutAnimation; @override Widget build(BuildContext context, WidgetRef ref) { - final prevNextChapterPair = ref.watch( - getPreviousAndNextChaptersProvider( + final nextPrevChapterPair = ref.watch( + getNextAndPreviousChaptersProvider( mangaId: manga.id!, chapterIndex: "${chapter.index}", ), @@ -76,7 +79,7 @@ class ReaderWrapper extends HookConsumerWidget { ref.watch(readerMagnifierSizeKeyProvider) ?? DBKeys.readerMagnifierSize.initial; - final visibility = useState(true); + final visibility = ref.watch(readerInitialOverlayProvider).ifNull(); final mangaReaderPadding = useState(manga.meta?.readerPadding ?? localMangaReaderPadding); final mangaReaderMagnifierSize = useState( @@ -138,11 +141,11 @@ class ReaderWrapper extends HookConsumerWidget { ); useEffect(() { - if (!visibility.value) { + if (!visibility) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); } return null; - }, [visibility.value]); + }, [visibility]); return Theme( data: context.theme.copyWith( @@ -152,7 +155,7 @@ class ReaderWrapper extends HookConsumerWidget { ), ), child: Scaffold( - appBar: visibility.value + appBar: visibility ? AppBar( title: ListTile( title: (manga.title).isNotBlank @@ -246,7 +249,7 @@ class ReaderWrapper extends HookConsumerWidget { ], ), ), - bottomSheet: visibility.value + bottomSheet: visibility ? ExcludeFocus( child: Column( mainAxisSize: MainAxisSize.min, @@ -256,12 +259,12 @@ class ReaderWrapper extends HookConsumerWidget { Card( shape: const CircleBorder(), child: IconButton( - onPressed: prevNextChapterPair?.second != null + onPressed: nextPrevChapterPair?.second != null ? () => ReaderRoute( mangaId: - prevNextChapterPair!.second!.mangaId!, + nextPrevChapterPair!.second!.mangaId!, chapterIndex: - prevNextChapterPair.second!.index!, + nextPrevChapterPair.second!.index!, toPrev: true, transVertical: scrollDirection != Axis.vertical, @@ -283,12 +286,12 @@ class ReaderWrapper extends HookConsumerWidget { Card( shape: const CircleBorder(), child: IconButton( - onPressed: prevNextChapterPair?.first != null + onPressed: nextPrevChapterPair?.first != null ? () => ReaderRoute( mangaId: - prevNextChapterPair!.first!.mangaId!, + nextPrevChapterPair!.first!.mangaId!, chapterIndex: - prevNextChapterPair.first!.index!, + nextPrevChapterPair.first!.index!, transVertical: scrollDirection != Axis.vertical, ).pushReplacement(context) @@ -363,10 +366,10 @@ class ReaderWrapper extends HookConsumerWidget { ), PreviousChapterIntent: CallbackAction( onInvoke: (intent) { - prevNextChapterPair?.second != null + nextPrevChapterPair?.second != null ? ReaderRoute( - mangaId: prevNextChapterPair!.second!.mangaId!, - chapterIndex: prevNextChapterPair.second!.index!, + mangaId: nextPrevChapterPair!.second!.mangaId!, + chapterIndex: nextPrevChapterPair.second!.index!, toPrev: true, transVertical: scrollDirection != Axis.vertical, ).pushReplacement(context) @@ -375,17 +378,19 @@ class ReaderWrapper extends HookConsumerWidget { }, ), NextChapterIntent: CallbackAction( - onInvoke: (intent) => prevNextChapterPair?.first != null + onInvoke: (intent) => nextPrevChapterPair?.first != null ? ReaderRoute( - mangaId: prevNextChapterPair!.first!.mangaId!, - chapterIndex: prevNextChapterPair.first!.index!, + mangaId: nextPrevChapterPair!.first!.mangaId!, + chapterIndex: nextPrevChapterPair.first!.index!, transVertical: scrollDirection != Axis.vertical, ).pushReplacement(context) : onNext(), ), HideQuickOpenIntent: CallbackAction( onInvoke: (HideQuickOpenIntent intent) { - visibility.value = (!visibility.value); + ref + .read(readerInitialOverlayProvider.notifier) + .update(!visibility); return null; }, ), @@ -394,16 +399,18 @@ class ReaderWrapper extends HookConsumerWidget { autofocus: true, child: RepaintBoundary( child: ReaderView( - toggleVisibility: () => - visibility.value = (!visibility.value), + toggleVisibility: () => ref + .read(readerInitialOverlayProvider.notifier) + .update(!visibility), scrollDirection: scrollDirection, mangaReaderPadding: mangaReaderPadding.value, mangaReaderMagnifierSize: mangaReaderMagnifierSize.value, onNext: onNext, onPrevious: onPrevious, mangaReaderNavigationLayout: mangaReaderNavigationLayout, - prevNextChapterPair: prevNextChapterPair, + prevNextChapterPair: nextPrevChapterPair, readerSwipeChapterToggle: readerSwipeChapterToggle, + showReaderLayoutAnimation: showReaderLayoutAnimation, child: child, ), ), @@ -416,19 +423,19 @@ 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, - }); + 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}); final VoidCallback toggleVisibility; final Axis scrollDirection; @@ -439,6 +446,7 @@ class ReaderView extends HookWidget { final ({Chapter? first, Chapter? second})? prevNextChapterPair; final ReaderNavigationLayout mangaReaderNavigationLayout; final bool readerSwipeChapterToggle; + final bool showReaderLayoutAnimation; final Widget child; @override @@ -517,6 +525,7 @@ class ReaderView extends HookWidget { onNext: onNext, onPrevious: onPrevious, navigationLayout: mangaReaderNavigationLayout, + showReaderLayoutAnimation: showReaderLayoutAnimation, ), if (showMagnification.value) Positioned( diff --git a/lib/src/features/manga_book/presentation/updates/updates_screen.dart b/lib/src/features/manga_book/presentation/updates/updates_screen.dart index 9427744d..dbaa837c 100644 --- a/lib/src/features/manga_book/presentation/updates/updates_screen.dart +++ b/lib/src/features/manga_book/presentation/updates/updates_screen.dart @@ -12,6 +12,7 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import '../../../../utils/extensions/custom_extensions.dart'; import '../../../../utils/hooks/paging_controller_hook.dart'; import '../../../../utils/misc/toast/toast.dart'; +import '../../../../widgets/custom_circular_progress_indicator.dart'; import '../../../../widgets/emoticons.dart'; import '../../data/updates/updates_repository.dart'; import '../../domain/chapter/chapter_model.dart'; @@ -114,6 +115,8 @@ class UpdatesScreen extends HookConsumerWidget { child: PagedListView( pagingController: controller, builderDelegate: PagedChildBuilderDelegate( + firstPageProgressIndicatorBuilder: (context) => + const CenterSorayomiShimmerIndicator(), firstPageErrorIndicatorBuilder: (context) => Emoticons( text: controller.error.toString(), button: TextButton( diff --git a/lib/src/features/manga_book/presentation/updates/widgets/chapter_manga_list_tile.dart b/lib/src/features/manga_book/presentation/updates/widgets/chapter_manga_list_tile.dart index 757d458b..f5c58863 100644 --- a/lib/src/features/manga_book/presentation/updates/widgets/chapter_manga_list_tile.dart +++ b/lib/src/features/manga_book/presentation/updates/widgets/chapter_manga_list_tile.dart @@ -92,6 +92,7 @@ class ChapterMangaListTile extends StatelessWidget { ReaderRoute( mangaId: pair.manga!.id!, chapterIndex: pair.chapter!.index!, + showReaderLayoutAnimation: true, ).push(context); } } diff --git a/lib/src/features/quick_open/presentation/quick_search/widgets/category_query_list_tile.dart b/lib/src/features/quick_open/presentation/quick_search/widgets/category_query_list_tile.dart index 010d05c3..e688e71e 100644 --- a/lib/src/features/quick_open/presentation/quick_search/widgets/category_query_list_tile.dart +++ b/lib/src/features/quick_open/presentation/quick_search/widgets/category_query_list_tile.dart @@ -36,8 +36,7 @@ class CategoryQueryListTile extends StatelessWidget { } else if ((chapter?.name).isBlank) { title = (manga?.title ?? ""); } else { - title = chapter?.name ?? - context.l10n!.chapterNumber(chapter?.chapterNumber ?? 0); + title = chapter?.getDisplayName(context) ?? ""; } return InkWell( @@ -46,6 +45,7 @@ class CategoryQueryListTile extends StatelessWidget { ReaderRoute( mangaId: manga!.id!, chapterIndex: chapter!.index!, + showReaderLayoutAnimation: true, ).push(context); } else if (manga?.id != null) { MangaRoute(mangaId: manga!.id!, categoryId: category?.id) 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 246e37ff..c5415a47 100644 --- a/lib/src/features/settings/presentation/reader/reader_settings_screen.dart +++ b/lib/src/features/settings/presentation/reader/reader_settings_screen.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import '../../../../utils/extensions/custom_extensions.dart'; +import 'widgets/reader_initial_overlay_tile/reader_initial_overlay_tile.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'; @@ -27,6 +28,7 @@ class ReaderSettingsScreen extends StatelessWidget { ReaderModeTile(), ReaderNavigationLayoutTile(), ReaderInvertTapTile(), + ReaderInitialOverlayTile(), SwipeChapterToggleTile(), ReaderScrollAnimationTile(), ReaderPaddingSlider(), diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_initial_overlay_tile/reader_initial_overlay_tile.dart b/lib/src/features/settings/presentation/reader/widgets/reader_initial_overlay_tile/reader_initial_overlay_tile.dart new file mode 100644 index 00000000..d801a4e0 --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_initial_overlay_tile/reader_initial_overlay_tile.dart @@ -0,0 +1,41 @@ +// 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 '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_initial_overlay_tile.g.dart'; + +@riverpod +class ReaderInitialOverlay extends _$ReaderInitialOverlay + with SharedPreferenceClientMixin { + @override + bool? build() => initialize( + ref, + key: DBKeys.readerOverlay.name, + initial: DBKeys.readerOverlay.initial, + ); +} + +class ReaderInitialOverlayTile extends HookConsumerWidget { + const ReaderInitialOverlayTile({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + return SwitchListTile( + controlAffinity: ListTileControlAffinity.trailing, + secondary: const Icon(Icons.layers_outlined), + title: Text(context.l10n!.readerOverlay), + onChanged: ref.read(readerInitialOverlayProvider.notifier).update, + value: ref.watch(readerInitialOverlayProvider).ifNull(), + ); + } +} diff --git a/lib/src/features/settings/presentation/reader/widgets/reader_initial_overlay_tile/reader_initial_overlay_tile.g.dart b/lib/src/features/settings/presentation/reader/widgets/reader_initial_overlay_tile/reader_initial_overlay_tile.g.dart new file mode 100644 index 00000000..81454654 --- /dev/null +++ b/lib/src/features/settings/presentation/reader/widgets/reader_initial_overlay_tile/reader_initial_overlay_tile.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reader_initial_overlay_tile.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$readerInitialOverlayHash() => + r'0cdf556eafc512165da2ba3b955544a187f03aad'; + +/// See also [ReaderInitialOverlay]. +@ProviderFor(ReaderInitialOverlay) +final readerInitialOverlayProvider = + AutoDisposeNotifierProvider.internal( + ReaderInitialOverlay.new, + name: r'readerInitialOverlayProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$readerInitialOverlayHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ReaderInitialOverlay = 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 1fc5b10f..f81d1fae 100644 --- a/lib/src/l10n/app_en.arb +++ b/lib/src/l10n/app_en.arb @@ -325,6 +325,9 @@ } } }, + "@nextChapter": { + "description": "Text for Next Chapter button in Manga Reader Screen" + }, "@newUpdateAvailable": { "description": "Popup title to show that the App/Server has and update" }, @@ -409,6 +412,9 @@ "@pending": { "description": "Pending status Manga Group title in Update Summary Screen" }, + "@previousChapter": { + "description": "Text for Previous Chapter button in Manga Reader Screen" + }, "@quickSearchContext": { "description": "Quick Open Hint text for query X" }, @@ -496,6 +502,9 @@ "@readerPadding": { "description": "Slider title text for Reader Padding" }, + "@readerOverlay": { + "description": "Toggle tile text for Initial Reader Overlay" + }, "@reddit": { "description": "Reddit app title" }, @@ -756,6 +765,7 @@ "moveToTop": "Move to top", "nameCountDisplay": "{name}: {count}", "newUpdateAvailable": "New update available", + "nextChapter": "Next: {chapterTitle}", "noCategoriesFound": "You don't have any Categories. \n(Tip: Tap the Plus button to create one for organizing your library)", "noCategoriesFoundAlt": "You don't have any Categories. \nCreate one in settings for organizing your library", "noCategoryMangaFound": "No manga found in this Category. \n(Tip: Check your search & filters!)", @@ -777,6 +787,7 @@ "password": "Password", "pause": "Pause", "pending": "Pending", + "previousChapter": "Previous: {chapterTitle}", "quickSearchCategory": "Go to Category 'C'", "quickSearchContext": "Search for query X (Results are based on screen context)", "quickSearchSource": "Go to Source 'S'", @@ -804,6 +815,7 @@ "readerNavigationLayoutKindlish": "Kindle-ish", "readerNavigationLayoutLShaped": "L Shaped", "readerNavigationLayoutRightAndLeft": "Right And Left", + "readerOverlay": "Reader initial overlay", "readerPadding": "Reader Padding", "readerMagnifierSize": "Magnifier Size", "reddit": "Reddit", diff --git a/lib/src/routes/router_config.dart b/lib/src/routes/router_config.dart index 44e5e36f..15eab9b0 100644 --- a/lib/src/routes/router_config.dart +++ b/lib/src/routes/router_config.dart @@ -268,11 +268,13 @@ class ReaderRoute extends GoRouteData { required this.chapterIndex, this.transVertical, this.toPrev, + this.showReaderLayoutAnimation = false, }); final int mangaId; final int chapterIndex; final bool? transVertical; final bool? toPrev; + final bool showReaderLayoutAnimation; static final $parentNavigatorKey = _quickOpenNavigatorKey; @@ -280,7 +282,11 @@ class ReaderRoute extends GoRouteData { Page buildPage(BuildContext context, GoRouterState state) { return CustomTransitionPage( key: state.pageKey, - child: ReaderScreen(mangaId: mangaId, chapterIndex: chapterIndex), + child: ReaderScreen( + mangaId: mangaId, + chapterIndex: chapterIndex, + showReaderLayoutAnimation: showReaderLayoutAnimation, + ), transitionsBuilder: (context, animation, secondaryAnimation, child) { Offset offset = Offset.zero; offset += Offset( diff --git a/lib/src/routes/router_config.g.dart b/lib/src/routes/router_config.g.dart index 7fe742c0..419a6622 100644 --- a/lib/src/routes/router_config.g.dart +++ b/lib/src/routes/router_config.g.dart @@ -393,6 +393,11 @@ extension $ReaderRouteExtension on ReaderRoute { 'trans-vertical', state.uri.queryParameters, _$boolConverter), toPrev: _$convertMapValue( 'to-prev', state.uri.queryParameters, _$boolConverter), + showReaderLayoutAnimation: _$convertMapValue( + 'show-reader-layout-animation', + state.uri.queryParameters, + _$boolConverter) ?? + false, ); String get location => GoRouteData.$location( @@ -401,6 +406,9 @@ extension $ReaderRouteExtension on ReaderRoute { if (transVertical != null) 'trans-vertical': transVertical!.toString(), if (toPrev != null) 'to-prev': toPrev!.toString(), + if (showReaderLayoutAnimation != false) + 'show-reader-layout-animation': + showReaderLayoutAnimation.toString(), }, ); diff --git a/lib/src/utils/extensions/custom_extensions/async_value_extensions.dart b/lib/src/utils/extensions/custom_extensions/async_value_extensions.dart index c2285a43..83b34fae 100644 --- a/lib/src/utils/extensions/custom_extensions/async_value_extensions.dart +++ b/lib/src/utils/extensions/custom_extensions/async_value_extensions.dart @@ -59,7 +59,7 @@ extension AsyncValueExtensions on AsyncValue { : null, )), loading: () => - AppUtils.wrapIf(wrapper, const CenterCircularProgressIndicator()), + AppUtils.wrapIf(wrapper, const CenterSorayomiShimmerIndicator()), ); } diff --git a/lib/src/utils/extensions/custom_extensions/context_extensions.dart b/lib/src/utils/extensions/custom_extensions/context_extensions.dart index 70dfd4f6..da19ec6d 100644 --- a/lib/src/utils/extensions/custom_extensions/context_extensions.dart +++ b/lib/src/utils/extensions/custom_extensions/context_extensions.dart @@ -186,6 +186,8 @@ extension ContextExtensions on BuildContext { Locale get currentLocale => Localizations.localeOf(this); + ColorScheme get colorScheme => Theme.of(this).colorScheme; + /// Returns a specific value according to the screen size /// if the device width is greater than or equal to 1200 return /// [desktop] value. if the device width is greater than or equal to 600 diff --git a/lib/src/widgets/custom_circular_progress_indicator.dart b/lib/src/widgets/custom_circular_progress_indicator.dart index 606bb88f..93fe01a4 100644 --- a/lib/src/widgets/custom_circular_progress_indicator.dart +++ b/lib/src/widgets/custom_circular_progress_indicator.dart @@ -5,16 +5,37 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; import '../constants/app_sizes.dart'; +import '../constants/gen/assets.gen.dart'; +import '../utils/extensions/custom_extensions.dart'; -class CenterCircularProgressIndicator extends StatelessWidget { - const CenterCircularProgressIndicator({super.key, this.value}); +class CenterSorayomiShimmerIndicator extends StatelessWidget { + const CenterSorayomiShimmerIndicator({super.key, this.value}); final double? value; @override - Widget build(BuildContext context) => - Center(child: CircularProgressIndicator(value: value)); + Widget build(BuildContext context) => Center( + child: SizedBox( + height: context.height * .35, + width: context.width * .35, + child: const SorayomiShimmerIndicator(), + ), + ); +} + +class SorayomiShimmerIndicator extends StatelessWidget { + const SorayomiShimmerIndicator({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: context.colorScheme.background, + highlightColor: context.theme.indicatorColor, + child: ImageIcon(AssetImage(Assets.icons.darkIcon.path)), + ); + } } class MiniCircularProgressIndicator extends StatelessWidget { diff --git a/lib/src/widgets/manga_cover/grid/manga_cover_grid_tile.dart b/lib/src/widgets/manga_cover/grid/manga_cover_grid_tile.dart index f088288a..b7dced7a 100644 --- a/lib/src/widgets/manga_cover/grid/manga_cover_grid_tile.dart +++ b/lib/src/widgets/manga_cover/grid/manga_cover_grid_tile.dart @@ -7,7 +7,9 @@ import 'package:flutter/material.dart'; import '../../../constants/app_sizes.dart'; +import '../../../constants/gen/assets.gen.dart'; import '../../../features/manga_book/domain/manga/manga_model.dart'; +import '../../../features/manga_book/presentation/manga_thumbnail_viewer/manga_thumbnail_viewer.dart'; import '../../../utils/extensions/custom_extensions.dart'; import '../../server_image.dart'; import '../widgets/manga_badges.dart'; @@ -33,7 +35,34 @@ class MangaCoverGridTile extends StatelessWidget { @override Widget build(BuildContext context) { return InkResponse( - onTap: onPressed, + onTap: onPressed ?? + () => Navigator.push( + context, + PageRouteBuilder( + fullscreenDialog: true, + opaque: false, + pageBuilder: (context, _, __) => MangaThumbnailViewer( + imageUrl: manga.thumbnailUrl ?? "", + ), + transitionsBuilder: + (context, animation, secondaryAnimation, child) { + const begin = Offset(-1.0, 0.0); + const end = Offset.zero; + const curve = Curves.ease; + + final tween = Tween(begin: begin, end: end); + final curvedAnimation = CurvedAnimation( + parent: animation, + curve: curve, + ); + + return SlideTransition( + position: tween.animate(curvedAnimation), + child: child, + ); + }, + ), + ), onLongPress: onLongPress, child: Card( clipBehavior: Clip.antiAlias, @@ -83,9 +112,8 @@ class MangaCoverGridTile extends StatelessWidget { ) : SizedBox( height: context.height * .3, - child: Icon( - Icons.book_rounded, - color: Colors.grey, + child: ImageIcon( + AssetImage(Assets.icons.darkIcon.path), size: context.height * .2, ), ), diff --git a/lib/src/widgets/server_image.dart b/lib/src/widgets/server_image.dart index 24e5cc1e..bfe94498 100644 --- a/lib/src/widgets/server_image.dart +++ b/lib/src/widgets/server_image.dart @@ -20,6 +20,7 @@ import '../features/settings/widgets/server_url_tile/server_url_tile.dart'; import '../global_providers/global_providers.dart'; import '../utils/extensions/custom_extensions.dart'; import '../utils/misc/app_utils.dart'; +import 'custom_circular_progress_indicator.dart'; class ServerImage extends HookConsumerWidget { const ServerImage({ @@ -69,13 +70,13 @@ class ServerImage extends HookConsumerWidget { renderMethod = ImageRenderMethodForWeb.HtmlImage; } - final finalProgressIndicatorBuilder = progressIndicatorBuilder != null - ? (BuildContext context, String url, DownloadProgress progress) => - AppUtils.wrapIf( - wrapper, - progressIndicatorBuilder!(context, url, progress), - ) - : null; + finalProgressIndicatorBuilder( + BuildContext context, String url, DownloadProgress progress) => + AppUtils.wrapIf( + wrapper, + progressIndicatorBuilder?.call(context, url, progress) ?? + const CenterSorayomiShimmerIndicator(), + ); Widget errorWidget(BuildContext context, String error, stackTrace) { if (showReloadButton) { @@ -157,6 +158,8 @@ class ServerImageWithCpi extends StatelessWidget { ServerImage( imageUrl: url, size: innerSize, + progressIndicatorBuilder: (context, url, progress) => + const CenterSorayomiShimmerIndicator(), ) ], ), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a685070c..916bd8c6 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,7 +14,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) diff --git a/pubspec.lock b/pubspec.lock index 70abace7..9415b8c9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: archive - sha256: "06a96f1249f38a00435b3b0c9a3246d934d7dbc8183fc7c9e56989860edb99d4" + sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" url: "https://pub.dev" source: hosted - version: "3.4.4" + version: "3.4.6" args: dependency: transitive description: @@ -325,18 +325,18 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_picker: dependency: "direct main" description: name: file_picker - sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030 + sha256: "903dd4ba13eae7cef64acc480e91bf54c3ddd23b5b90b639c170f3911e489620" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "6.0.0" fixnum: dependency: transitive description: @@ -378,10 +378,10 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: "4618087243264d5f745b2a719a9fc610768f723da570efc2d0eaefe1efcbae70" + sha256: "7c8db779c2d1010aa7f9ea3fbefe8f86524fcb87b69e8b0af31e1a4b55422dec" url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.20.3" flutter_launcher_icons: dependency: "direct dev" description: @@ -407,10 +407,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: "519bf9e1d5df40dbb77d2f0a357a40fadf6b72e4dc44301916b81cad294db676" + sha256: "8afc9a6aa6d8e8063523192ba837149dbf3d377a37c0b0fc579149a1fbd4a619" url: "https://pub.dev" source: hosted - version: "0.6.17+4" + version: "0.6.18" flutter_native_splash: dependency: "direct dev" description: @@ -431,10 +431,10 @@ packages: dependency: transitive description: name: flutter_riverpod - sha256: "890a872705cdbda11efd2bff2f672f1c8180b86b9509490822ee96a3e20cd4aa" + sha256: e667e406a74d67715f1fa0bd941d9ded49aff72f3a9f4440a36aece4e8d457a7 url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" flutter_staggered_grid_view: dependency: transitive description: @@ -473,10 +473,10 @@ packages: dependency: "direct dev" description: name: freezed - sha256: be7826ed5d87e98c924a839542674fc14edbcb3e4fc0adbc058d680f2b241837 + sha256: "21bf2825311de65501d22e563e3d7605dff57fb5e6da982db785ae5372ff018a" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.5" freezed_annotation: dependency: "direct main" description: @@ -505,10 +505,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: a07c781bf55bf11ae85133338e4850f0b4e33e261c44a66c750fc707d65d8393 + sha256: "2ccd74480706e0a70a0e0dfa9543dede41bc11d0fe3b146a6ad7b7686f6b4407" url: "https://pub.dev" source: hosted - version: "11.1.2" + version: "11.1.4" go_router_builder: dependency: "direct dev" description: @@ -537,10 +537,10 @@ packages: dependency: "direct main" description: name: hooks_riverpod - sha256: ffd7078949ac3903d53f2a58c5f62c3031b60b2a8b0765c4f2cf0f82021b1fad + sha256: "69dcb88acbc68c81fc27ec15a89a4e24b7812c83c13a6307a1a9366ada758541" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" html: dependency: transitive description: @@ -689,10 +689,10 @@ packages: dependency: "direct main" description: name: network_info_plus - sha256: c0113bfd4276be3fe4ba56aeb2792d780df9f3f62aaa4afdd1cdf716b8e29545 + sha256: "2d9e88b9a459e5d4e224f828d26cc38ea140511e89b943116939994324be5c96" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.0" network_info_plus_platform_interface: dependency: transitive description: @@ -729,10 +729,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -801,18 +801,18 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: ad65ba9af42a3d067203641de3fd9f547ded1410bad3b84400c2b4899faede70 + sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" url: "https://pub.dev" source: hosted - version: "11.0.0" + version: "11.0.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: ace7d15a3d1a4a0b91c041d01e5405df221edb9de9116525efc773c74e6fc790 + sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e url: "https://pub.dev" source: hosted - version: "11.0.5" + version: "11.1.0" permission_handler_apple: dependency: transitive description: @@ -825,10 +825,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: f2343e9fa9c22ae4fd92d4732755bfe452214e7189afcc097380950cf567b4b2 + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" url: "https://pub.dev" source: hosted - version: "3.11.5" + version: "3.12.0" permission_handler_windows: dependency: transitive description: @@ -849,10 +849,10 @@ packages: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" plugin_platform_interface: dependency: transitive description: @@ -905,34 +905,34 @@ packages: dependency: transitive description: name: riverpod - sha256: ff676bd8a715c7085692fe4919564f78fb90d33b10a1c5c14e740581857cc914 + sha256: "494bf2cfb4df30000273d3052bdb1cc1de738574c6b678f0beb146ea56f5e208" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: aa216069d72f5478126029fa555874b4b38119f17e3f0f6c93fd63365f74502d + sha256: d72d7096964baf288b55619fe48100001fc4564ab7923ed0a7f5c7650e03c0d6 url: "https://pub.dev" source: hosted - version: "0.3.3" + version: "0.3.4" riverpod_annotation: dependency: "direct main" description: name: riverpod_annotation - sha256: aeeb1eb6ccf2d779f2ef730e6d96d560316b677662222316779a8cf0a94ee317 + sha256: b724b2085405c4f62a1824a3fa3fb1e4858e09a4e2e8db92b7547c08d07a1377 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.2.0" riverpod_generator: dependency: "direct dev" description: name: riverpod_generator - sha256: d132b1ccb476e60f99989caa6ba9b1c4d88409806c93d880d1633c60c382454d + sha256: "5b36ad2f2b562cffb37212e8d59390b25499bf045b732276e30a207b16a25f61" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" rxdart: dependency: transitive description: @@ -953,10 +953,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_android: dependency: transitive description: @@ -977,10 +977,10 @@ packages: dependency: transitive description: name: shared_preferences_linux - sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_platform_interface: dependency: transitive description: @@ -1001,10 +1001,10 @@ packages: dependency: transitive description: name: shared_preferences_windows - sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shelf: dependency: transitive description: @@ -1021,6 +1021,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 7221b784..4818680b 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.5.11+1 +version: 0.5.12+1 environment: sdk: ">=3.0.0 <4.0.0" @@ -14,7 +14,7 @@ dependencies: dio: ^5.0.0 dio_cache_interceptor: ^3.4.0 dio_cache_interceptor_hive_store: ^3.2.1 - file_picker: ^5.2.2 + file_picker: ^6.0.0 flutter: sdk: flutter flutter_cache_manager: ^3.3.0 @@ -40,6 +40,7 @@ dependencies: riverpod_annotation: ^2.0.0 scrollable_positioned_list: ^0.3.5 shared_preferences: ^2.0.15 + shimmer: ^3.0.0 url_launcher: ^6.1.6 web_socket_channel: ^2.2.0