diff --git a/lib/main.dart b/lib/main.dart index 61c47860..699bafd4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,31 +3,25 @@ import 'dart:io'; import 'package:app_links/app_links.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:desktop_webview_window/desktop_webview_window.dart'; -import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:intl/date_symbol_data_local.dart'; -import 'package:intl/intl.dart'; import 'package:isar/isar.dart'; import 'package:mangayomi/eval/model/m_bridge.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/more/data_and_storage/providers/storage_usage.dart'; -import 'package:mangayomi/modules/more/settings/appearance/providers/app_font_family.dart'; import 'package:mangayomi/modules/more/settings/browse/providers/browse_state_provider.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; import 'package:mangayomi/providers/storage_provider.dart'; import 'package:mangayomi/router/router.dart'; -import 'package:mangayomi/modules/more/settings/appearance/providers/blend_level_state_provider.dart'; -import 'package:mangayomi/modules/more/settings/appearance/providers/flex_scheme_color_state_provider.dart'; -import 'package:mangayomi/modules/more/settings/appearance/providers/pure_black_dark_mode_state_provider.dart'; import 'package:mangayomi/modules/more/settings/appearance/providers/theme_mode_state_provider.dart'; import 'package:mangayomi/l10n/generated/app_localizations.dart'; import 'package:mangayomi/src/rust/frb_generated.dart'; import 'package:mangayomi/utils/url_protocol/api.dart'; +import 'package:mangayomi/modules/more/settings/appearance/providers/theme_provider.dart'; import 'package:media_kit/media_kit.dart'; import 'package:path_provider/path_provider.dart'; import 'package:window_manager/window_manager.dart'; @@ -37,11 +31,7 @@ late Isar isar; WebViewEnvironment? webViewEnvironment; void main(List args) async { WidgetsFlutterBinding.ensureInitialized(); - if (Platform.isLinux) { - if (runWebViewTitleBarWidget(args)) { - return; - } - } + if (Platform.isLinux && runWebViewTitleBarWidget(args)) return; MediaKit.ensureInitialized(); await RustLib.init(); if (!(Platform.isAndroid || Platform.isIOS)) { @@ -62,19 +52,14 @@ void main(List args) async { } } isar = await StorageProvider().initDB(null, inspector: kDebugMode); - await StorageProvider().requestPermission(); - await StorageProvider().deleteBtDirectory(); - GoogleFonts.aBeeZee(); runApp(const ProviderScope(child: MyApp())); + unawaited(postLaunchInit()); // Defer non-essential async operations } -void _iniDateFormatting() { - initializeDateFormatting(); - final supportedLocales = DateFormat.allLocalesWithSymbols(); - for (var locale in supportedLocales) { - initializeDateFormatting(locale); - } +Future postLaunchInit() async { + await StorageProvider().requestPermission(); + await StorageProvider().deleteBtDirectory(); } class MyApp extends ConsumerStatefulWidget { @@ -87,11 +72,12 @@ class MyApp extends ConsumerStatefulWidget { class _MyAppState extends ConsumerState { late AppLinks _appLinks; StreamSubscription? _linkSubscription; + Uri? lastUri; @override void initState() { super.initState(); - _iniDateFormatting(); + initializeDateFormatting(); _initDeepLinks(); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -100,67 +86,24 @@ class _MyAppState extends ConsumerState { .read(totalChapterCacheSizeStateProvider.notifier) .clearCache(showToast: false); } - // Check if System theme has changed since last app start and adjust - if (ref.read(followSystemThemeStateProvider)) { - var brightness = - WidgetsBinding.instance.platformDispatcher.platformBrightness; - if (brightness == Brightness.light) { - ref.read(themeModeStateProvider.notifier).setLightTheme(); - } else { - ref.read(themeModeStateProvider.notifier).setDarkTheme(); - } - } }); } @override Widget build(BuildContext context) { - final isDarkTheme = ref.watch(themeModeStateProvider); - final blendLevel = ref.watch(blendLevelStateProvider); - final appFontFamily = ref.watch(appFontFamilyProvider); - final pureBlackDarkMode = ref.watch(pureBlackDarkModeStateProvider); + final followSystem = ref.watch(followSystemThemeStateProvider); + final forcedDark = ref.watch(themeModeStateProvider); + final themeMode = + followSystem + ? ThemeMode.system + : (forcedDark ? ThemeMode.dark : ThemeMode.light); final locale = ref.watch(l10nLocaleStateProvider); - ThemeData themeLight = FlexThemeData.light( - colors: ref.watch(flexSchemeColorStateProvider), - surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, - blendLevel: blendLevel.toInt(), - appBarOpacity: 0.00, - subThemesData: const FlexSubThemesData( - blendOnLevel: 10, - thinBorderWidth: 2.0, - unselectedToggleIsColored: true, - inputDecoratorRadius: 24.0, - chipRadius: 24.0, - ), - useMaterial3ErrorColors: true, - visualDensity: FlexColorScheme.comfortablePlatformDensity, - useMaterial3: true, - fontFamily: appFontFamily, - ); - ThemeData themeDark = FlexThemeData.dark( - colors: ref.watch(flexSchemeColorStateProvider), - surfaceMode: FlexSurfaceMode.level, - blendLevel: blendLevel.toInt(), - appBarOpacity: 0.00, - scaffoldBackground: pureBlackDarkMode ? Colors.black : null, - subThemesData: const FlexSubThemesData( - blendOnLevel: 10, - thinBorderWidth: 2.0, - unselectedToggleIsColored: true, - inputDecoratorRadius: 24.0, - chipRadius: 24.0, - ), - useMaterial3ErrorColors: true, - visualDensity: FlexColorScheme.comfortablePlatformDensity, - useMaterial3: true, - fontFamily: appFontFamily, - ); final router = ref.watch(routerProvider); return MaterialApp.router( - darkTheme: themeDark, - themeMode: isDarkTheme ? ThemeMode.dark : ThemeMode.light, - theme: themeLight, + theme: ref.watch(lightThemeProvider), + darkTheme: ref.watch(darkThemeProvider), + themeMode: themeMode, debugShowCheckedModeBanner: false, locale: locale, localizationsDelegates: AppLocalizations.localizationsDelegates, @@ -182,6 +125,8 @@ class _MyAppState extends ConsumerState { Future _initDeepLinks() async { _appLinks = AppLinks(); _linkSubscription = _appLinks.uriLinkStream.listen((uri) { + if (uri == lastUri) return; // Debouncing Deep Links + lastUri = uri; switch (uri.host) { case "add-repo": final repoName = uri.queryParameters["repo_name"]; @@ -190,8 +135,8 @@ class _MyAppState extends ConsumerState { final animeRepoUrls = uri.queryParametersAll["anime_url"]; final novelRepoUrls = uri.queryParametersAll["novel_url"]; final context = navigatorKey.currentContext; - if (!(context?.mounted ?? false)) return; - final l10n = context!.l10n; + if (context == null || !context.mounted) return; + final l10n = context.l10n; showDialog( context: navigatorKey.currentContext!, builder: (BuildContext context) { @@ -215,78 +160,30 @@ class _MyAppState extends ConsumerState { child: Text(l10n.add), onPressed: () { Navigator.of(context).pop(); - if (mangaRepoUrls != null) { - final mangaRepos = - ref - .read( - extensionsRepoStateProvider(ItemType.manga), - ) - .toList(); - mangaRepos.addAll( - mangaRepoUrls.map( + + void addRepos(ItemType type, List? urls) { + if (urls == null) return; + final current = ref.read( + extensionsRepoStateProvider(type), + ); + final updated = [ + ...current, + ...urls.map( (e) => Repo( name: repoName, jsonUrl: e, website: repoUrl, ), ), - ); + ]; ref - .read( - extensionsRepoStateProvider( - ItemType.manga, - ).notifier, - ) - .set(mangaRepos); - } - if (animeRepoUrls != null) { - final animeRepos = - ref - .read( - extensionsRepoStateProvider(ItemType.anime), - ) - .toList(); - animeRepos.addAll( - animeRepoUrls.map( - (e) => Repo( - name: repoName, - jsonUrl: e, - website: repoUrl, - ), - ), - ); - ref - .read( - extensionsRepoStateProvider( - ItemType.anime, - ).notifier, - ) - .set(animeRepos); - } - if (novelRepoUrls != null) { - final novelRepos = - ref - .read( - extensionsRepoStateProvider(ItemType.novel), - ) - .toList(); - novelRepos.addAll( - novelRepoUrls.map( - (e) => Repo( - name: repoName, - jsonUrl: e, - website: repoUrl, - ), - ), - ); - ref - .read( - extensionsRepoStateProvider( - ItemType.novel, - ).notifier, - ) - .set(novelRepos); + .read(extensionsRepoStateProvider(type).notifier) + .set(updated); } + + addRepos(ItemType.manga, mangaRepoUrls); + addRepos(ItemType.anime, animeRepoUrls); + addRepos(ItemType.novel, novelRepoUrls); botToast(l10n.repo_added); }, ), diff --git a/lib/modules/anime/anime_player_view.dart b/lib/modules/anime/anime_player_view.dart index a62b2818..951b5b29 100644 --- a/lib/modules/anime/anime_player_view.dart +++ b/lib/modules/anime/anime_player_view.dart @@ -305,6 +305,7 @@ class _AnimeStreamPageState extends riv.ConsumerState @override void initState() { + super.initState(); _currentPositionSub; _currentTotalDurationSub; _completed; @@ -332,7 +333,6 @@ class _AnimeStreamPageState extends riv.ConsumerState _setPlaybackSpeed(ref.read(defaultPlayBackSpeedStateProvider)); _initAniSkip(); }); - super.initState(); } Future _loadAndroidFont() async { diff --git a/lib/modules/anime/widgets/custom_seekbar.dart b/lib/modules/anime/widgets/custom_seekbar.dart index 1215851a..5f923641 100644 --- a/lib/modules/anime/widgets/custom_seekbar.dart +++ b/lib/modules/anime/widgets/custom_seekbar.dart @@ -31,6 +31,7 @@ class CustomSeekBarState extends State { @override void initState() { + super.initState(); player.stream.position.listen((event) { if (mounted) { setState(() { @@ -55,7 +56,6 @@ class CustomSeekBarState extends State { position = player.state.position; duration = player.state.duration; buffer = player.state.buffer; - super.initState(); } final isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux; diff --git a/lib/modules/anime/widgets/subtitle_view.dart b/lib/modules/anime/widgets/subtitle_view.dart index 625e2b7d..7bd379bd 100644 --- a/lib/modules/anime/widgets/subtitle_view.dart +++ b/lib/modules/anime/widgets/subtitle_view.dart @@ -40,12 +40,12 @@ class _CustomSubtitleViewState extends ConsumerState { @override void initState() { + super.initState(); subscription = widget.controller.player.stream.subtitle.listen((value) { setState(() { subtitle = value; }); }); - super.initState(); } @override diff --git a/lib/modules/browse/extension/edit_code.dart b/lib/modules/browse/extension/edit_code.dart index 8de1905d..4b6ef767 100644 --- a/lib/modules/browse/extension/edit_code.dart +++ b/lib/modules/browse/extension/edit_code.dart @@ -63,6 +63,7 @@ class _CodeEditorPageState extends ConsumerState { final _scrollController = ScrollController(); @override void initState() { + super.initState(); _controller.text = source?.sourceCode ?? ""; useLogger = true; _logStreamController.stream.asBroadcastStream().listen((event) async { @@ -72,7 +73,6 @@ class _CodeEditorPageState extends ConsumerState { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } catch (_) {} }); - super.initState(); } List filters = []; diff --git a/lib/modules/browse/global_search/global_search_screen.dart b/lib/modules/browse/global_search/global_search_screen.dart index 37179630..a7affc27 100644 --- a/lib/modules/browse/global_search/global_search_screen.dart +++ b/lib/modules/browse/global_search/global_search_screen.dart @@ -117,8 +117,8 @@ class SourceSearchScreen extends StatefulWidget { class _SourceSearchScreenState extends State { @override void initState() { - _init(); super.initState(); + _init(); } String _errorMessage = ""; diff --git a/lib/modules/history/history_screen.dart b/lib/modules/history/history_screen.dart index 426534b0..2bd57fa0 100644 --- a/lib/modules/history/history_screen.dart +++ b/lib/modules/history/history_screen.dart @@ -44,10 +44,10 @@ class _HistoryScreenState extends ConsumerState @override void initState() { + super.initState(); _tabBarController = TabController(length: tabs, vsync: this); _tabBarController.animateTo(0); _tabBarController.addListener(tabListener); - super.initState(); } final _textEditingController = TextEditingController(); diff --git a/lib/modules/library/widgets/list_tile_manga_category.dart b/lib/modules/library/widgets/list_tile_manga_category.dart index f612a7c5..f80736b8 100644 --- a/lib/modules/library/widgets/list_tile_manga_category.dart +++ b/lib/modules/library/widgets/list_tile_manga_category.dart @@ -25,6 +25,7 @@ class ListTileMangaCategory extends StatefulWidget { class _ListTileMangaCategoryState extends State { @override void initState() { + super.initState(); final res = widget.mangasList.where((element) { return element.categories == null @@ -32,7 +33,6 @@ class _ListTileMangaCategoryState extends State { : element.categories!.contains(widget.category.id); }).toList(); widget.res(res); - super.initState(); } @override diff --git a/lib/modules/main_view/main_screen.dart b/lib/modules/main_view/main_screen.dart index b64cf99a..d63a2c5e 100644 --- a/lib/modules/main_view/main_screen.dart +++ b/lib/modules/main_view/main_screen.dart @@ -38,6 +38,8 @@ class MainScreen extends ConsumerStatefulWidget { } class _MainScreenState extends ConsumerState { + Timer? _backupTimer; + Timer? _syncTimer; String getHyphenatedUpdatesLabel(String languageCode, String defaultLabel) { switch (languageCode) { case 'de': @@ -63,26 +65,19 @@ class _MainScreenState extends ConsumerState { late String defaultLocation = navigationOrder.first; @override initState() { + super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.go(defaultLocation); - Timer.periodic(Duration(minutes: 5), (timer) { - ref.read(checkAndBackupProvider); - }); + _backupTimer = Timer.periodic( + const Duration(minutes: 5), + _onBackupTimerTick, + ); if (autoSyncFrequency != 0) { - final l10n = l10nLocalizations(context)!; - Timer.periodic(Duration(seconds: autoSyncFrequency), (timer) { - try { - ref - .read(syncServerProvider(syncId: 1).notifier) - .startSync(l10n, true); - } catch (e) { - botToast( - "Failed to sync! Maybe the sync server is down. Restart the app to resume auto sync.", - ); - timer.cancel(); - } - }); + _syncTimer = Timer.periodic( + Duration(seconds: autoSyncFrequency), + _onSyncTimerTick, + ); } ref.watch(checkForUpdateProvider(context: context)); @@ -90,8 +85,38 @@ class _MainScreenState extends ConsumerState { ref.watch(fetchAnimeSourcesListProvider(id: null, reFresh: false)); ref.watch(fetchNovelSourcesListProvider(id: null, reFresh: false)); }); + } - super.initState(); + void _onBackupTimerTick(Timer timer) { + if (!mounted) { + timer.cancel(); + return; + } + ref.read(checkAndBackupProvider); + } + + void _onSyncTimerTick(Timer timer) { + if (!mounted) { + timer.cancel(); + return; + } + try { + final l10n = l10nLocalizations(context)!; + ref.read(syncServerProvider(syncId: 1).notifier).startSync(l10n, true); + } catch (e) { + botToast( + "Failed to sync! Maybe the sync server is down. " + "Restart the app to resume auto sync.", + ); + timer.cancel(); + } + } + + @override + void dispose() { + _backupTimer?.cancel(); + _syncTimer?.cancel(); + super.dispose(); } @override diff --git a/lib/modules/manga/detail/manga_detail_main.dart b/lib/modules/manga/detail/manga_detail_main.dart index 9f6c3bba..49438f30 100644 --- a/lib/modules/manga/detail/manga_detail_main.dart +++ b/lib/modules/manga/detail/manga_detail_main.dart @@ -20,8 +20,8 @@ class MangaReaderDetail extends ConsumerStatefulWidget { class _MangaReaderDetailState extends ConsumerState { @override void initState() { - _init(); super.initState(); + _init(); } _init() async { diff --git a/lib/modules/manga/detail/manga_detail_view.dart b/lib/modules/manga/detail/manga_detail_view.dart index 661ff0c6..26c7053c 100644 --- a/lib/modules/manga/detail/manga_detail_view.dart +++ b/lib/modules/manga/detail/manga_detail_view.dart @@ -82,11 +82,11 @@ class _MangaDetailViewState extends ConsumerState with TickerProviderStateMixin { @override void initState() { + super.initState(); _scrollController = ScrollController()..addListener(() { ref.read(offetProvider.notifier).state = _scrollController.offset; }); - super.initState(); } final offetProvider = StateProvider((ref) => 0.0); diff --git a/lib/modules/manga/detail/providers/track_state_providers.dart b/lib/modules/manga/detail/providers/track_state_providers.dart index 466fdab9..f6f8279c 100644 --- a/lib/modules/manga/detail/providers/track_state_providers.dart +++ b/lib/modules/manga/detail/providers/track_state_providers.dart @@ -54,24 +54,11 @@ class TrackState extends _$TrackState { ) .updateLibAnime(track!), }, - _ => switch (itemType) { - ItemType.manga => ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .updateLibManga(track!), - _ => ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .updateLibAnime(track!), - }, + _ => ref + .read( + kitsuProvider(syncId: track!.syncId!, itemType: itemType).notifier, + ) + .updateLib(track!, _isManga), }; ref @@ -205,18 +192,9 @@ class TrackState extends _$TrackState { ) .addLibAnime(track); } else if (syncId == 3) { - findManga = - _isManga - ? await ref - .read( - kitsuProvider(syncId: syncId, itemType: itemType).notifier, - ) - .addLibManga(track) - : await ref - .read( - kitsuProvider(syncId: syncId, itemType: itemType).notifier, - ) - .addLibAnime(track); + findManga = await ref + .read(kitsuProvider(syncId: syncId, itemType: itemType).notifier) + .addLib(track, _isManga); } ref @@ -266,24 +244,11 @@ class TrackState extends _$TrackState { ) .aniListStatusListAnime; } else if (track!.syncId == 3) { - statusList = - _isManga - ? ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .kitsuStatusListManga - : ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .kitsuStatusListAnime; + statusList = ref + .read( + kitsuProvider(syncId: track!.syncId!, itemType: itemType).notifier, + ) + .kitsuStatusList(_isManga); } for (var element in TrackStatus.values) { if (statusList.contains(element)) { @@ -324,24 +289,11 @@ class TrackState extends _$TrackState { ) .findLibAnime(track!); } else if (track!.syncId == 3) { - findManga = - _isManga - ? await ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .findLibManga(track!) - : await ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .findLibAnime(track!); + findManga = await ref + .read( + kitsuProvider(syncId: track!.syncId!, itemType: itemType).notifier, + ) + .findLibItem(track!, _isManga); } return findManga; } @@ -377,24 +329,11 @@ class TrackState extends _$TrackState { ) .searchAnime(query); } else if (track!.syncId == 3) { - tracks = - _isManga - ? await ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .search(query) - : await ref - .read( - kitsuProvider( - syncId: track!.syncId!, - itemType: itemType, - ).notifier, - ) - .searchAnime(query); + tracks = await ref + .read( + kitsuProvider(syncId: track!.syncId!, itemType: itemType).notifier, + ) + .search(query, _isManga); } return tracks; } diff --git a/lib/modules/manga/detail/widgets/chapter_list_tile_widget.dart b/lib/modules/manga/detail/widgets/chapter_list_tile_widget.dart index b110faa2..ca7520d0 100644 --- a/lib/modules/manga/detail/widgets/chapter_list_tile_widget.dart +++ b/lib/modules/manga/detail/widgets/chapter_list_tile_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:marquee/marquee.dart'; import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/providers/l10n_providers.dart'; @@ -64,13 +65,7 @@ class ChapterListTileWidget extends ConsumerWidget { chapter.isBookmarked! ? Icon(Icons.bookmark, size: 16, color: context.primaryColor) : Container(), - Flexible( - child: Text( - chapter.name!, - style: const TextStyle(fontSize: 13), - overflow: TextOverflow.ellipsis, - ), - ), + Flexible(child: _buildTitle(chapter.name!, context)), ], ), subtitle: Row( @@ -141,4 +136,40 @@ class ChapterListTileWidget extends ConsumerWidget { ), ); } + + Widget _buildTitle(String text, BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final textPainter = TextPainter( + text: TextSpan(text: text, style: const TextStyle(fontSize: 13)), + maxLines: 1, + textDirection: TextDirection.ltr, + )..layout( + maxWidth: (constraints.maxWidth - (35 + 5)), + ); // - Download icon size (download_page_widget.dart, Widget Build SizedBox width: 35) + + final isOverflowing = textPainter.didExceedMaxLines; + + if (isOverflowing) { + return SizedBox( + height: 20, + child: Marquee( + text: text, + style: const TextStyle(fontSize: 13), + blankSpace: 40.0, + velocity: 30.0, + pauseAfterRound: const Duration(seconds: 1), + startPadding: 10.0, + ), + ); + } else { + return Text( + text, + style: const TextStyle(fontSize: 13), + overflow: TextOverflow.ellipsis, + ); + } + }, + ); + } } diff --git a/lib/modules/manga/detail/widgets/migrate_screen.dart b/lib/modules/manga/detail/widgets/migrate_screen.dart index 0e9cae0a..21423567 100644 --- a/lib/modules/manga/detail/widgets/migrate_screen.dart +++ b/lib/modules/manga/detail/widgets/migrate_screen.dart @@ -93,8 +93,8 @@ class _MigrationSourceSearchScreenState extends State { @override void initState() { - _init(); super.initState(); + _init(); } String _errorMessage = ""; diff --git a/lib/modules/manga/detail/widgets/tracker_search_widget.dart b/lib/modules/manga/detail/widgets/tracker_search_widget.dart index 98039012..6445ce0a 100644 --- a/lib/modules/manga/detail/widgets/tracker_search_widget.dart +++ b/lib/modules/manga/detail/widgets/tracker_search_widget.dart @@ -29,8 +29,8 @@ class TrackerWidgetSearch extends ConsumerStatefulWidget { class _TrackerWidgetSearchState extends ConsumerState { @override initState() { - _init(); super.initState(); + _init(); } late String query = widget.track.title!.trim(); diff --git a/lib/modules/manga/detail/widgets/tracker_widget.dart b/lib/modules/manga/detail/widgets/tracker_widget.dart index 9d7d5fc2..d86e46e4 100644 --- a/lib/modules/manga/detail/widgets/tracker_widget.dart +++ b/lib/modules/manga/detail/widgets/tracker_widget.dart @@ -35,8 +35,8 @@ class TrackerWidget extends ConsumerStatefulWidget { class _TrackerWidgetState extends ConsumerState { @override initState() { - _init(); super.initState(); + _init(); } _init() async { @@ -491,7 +491,7 @@ class _TrackerWidgetState extends ConsumerState { text: widget.trackRes.startedReadingDate != null && widget.trackRes.startedReadingDate! > - DateTime(1970).millisecondsSinceEpoch + DateTime.utc(1970).millisecondsSinceEpoch ? dateFormat( widget.trackRes.startedReadingDate.toString(), ref: ref, @@ -532,7 +532,7 @@ class _TrackerWidgetState extends ConsumerState { text: widget.trackRes.finishedReadingDate != null && widget.trackRes.finishedReadingDate! > - DateTime(1970).millisecondsSinceEpoch + DateTime.utc(1970).millisecondsSinceEpoch ? dateFormat( widget.trackRes.finishedReadingDate.toString(), ref: ref, diff --git a/lib/modules/manga/reader/double_columm_view_center.dart b/lib/modules/manga/reader/double_columm_view_center.dart index beaceef8..42e22ba0 100644 --- a/lib/modules/manga/reader/double_columm_view_center.dart +++ b/lib/modules/manga/reader/double_columm_view_center.dart @@ -68,6 +68,7 @@ class _DoubleColummViewState extends State @override void initState() { + super.initState(); _scaleAnimationController = AnimationController( duration: _doubleTapAnimationDuration(), vsync: this, @@ -78,8 +79,6 @@ class _DoubleColummViewState extends State _animation.addListener(() { _photoViewController.scale = _animation.value; }); - - super.initState(); } void _toggleScale(Offset tapPosition) { diff --git a/lib/modules/manga/reader/reader_view.dart b/lib/modules/manga/reader/reader_view.dart index f8e4e75a..63755ae7 100644 --- a/lib/modules/manga/reader/reader_view.dart +++ b/lib/modules/manga/reader/reader_view.dart @@ -189,6 +189,7 @@ class _MangaChapterPageGalleryState StreamController.broadcast(); @override void initState() { + super.initState(); _doubleClickAnimationController = AnimationController( duration: _doubleTapAnimationDuration(), vsync: this, @@ -203,8 +204,6 @@ class _MangaChapterPageGalleryState _animation.addListener(() => _photoViewController.scale = _animation.value); _itemPositionsListener.itemPositions.addListener(_readProgressListener); _initCurrentIndex(); - - super.initState(); } final double _horizontalScaleValue = 1.0; @@ -960,53 +959,55 @@ class _MangaChapterPageGalleryState } void _readProgressListener() { - _currentIndex = _itemPositionsListener.itemPositions.value.first.index; + final itemPositions = _itemPositionsListener.itemPositions.value; - int pagesLength = - (_pageMode == PageMode.doublePage && - !(ref.watch(_currentReaderMode) == - ReaderMode.horizontalContinuous)) - ? (_uChapDataPreload.length / 2).ceil() + 1 - : _uChapDataPreload.length; + if (itemPositions.isNotEmpty) { + _currentIndex = itemPositions.first.index; + int pagesLength = + (_pageMode == PageMode.doublePage && + !(ref.watch(_currentReaderMode) == + ReaderMode.horizontalContinuous)) + ? (_uChapDataPreload.length / 2).ceil() + 1 + : _uChapDataPreload.length; - if (_currentIndex! >= 0 && _currentIndex! < pagesLength) { - if (_readerController.chapter.id != - _uChapDataPreload[_currentIndex!].chapter!.id) { - _readerController.setPageIndex( - _geCurrentIndex(_uChapDataPreload[_currentIndex! - 1].index!), - false, - ); - if (mounted) { - setState(() { - _readerController = ref.read( - readerControllerProvider( - chapter: _uChapDataPreload[_currentIndex!].chapter!, - ).notifier, - ); + if (_currentIndex! >= 0 && _currentIndex! < pagesLength) { + if (_readerController.chapter.id != + _uChapDataPreload[_currentIndex!].chapter!.id) { + _readerController.setPageIndex( + _geCurrentIndex(_uChapDataPreload[_currentIndex! - 1].index!), + false, + ); + if (mounted) { + setState(() { + _readerController = ref.read( + readerControllerProvider( + chapter: _uChapDataPreload[_currentIndex!].chapter!, + ).notifier, + ); - chapter = _uChapDataPreload[_currentIndex!].chapter!; - _chapterUrlModel = - _uChapDataPreload[_currentIndex!].chapterUrlModel!; - _isBookmarked = _readerController.getChapterBookmarked(); - }); + chapter = _uChapDataPreload[_currentIndex!].chapter!; + _chapterUrlModel = + _uChapDataPreload[_currentIndex!].chapterUrlModel!; + _isBookmarked = _readerController.getChapterBookmarked(); + }); + } + } + if (itemPositions.last.index == pagesLength - 1) { + try { + ref + .watch( + getChapterPagesProvider( + chapter: _readerController.getNextChapter(), + ).future, + ) + .then((value) => _preloadNextChapter(value, chapter)); + } catch (_) {} } - } - if (_itemPositionsListener.itemPositions.value.last.index == - pagesLength - 1) { - try { - ref - .watch( - getChapterPagesProvider( - chapter: _readerController.getNextChapter(), - ).future, - ) - .then((value) => _preloadNextChapter(value, chapter)); - } catch (_) {} - } - ref - .read(currentIndexProvider(chapter).notifier) - .setCurrentIndex(_uChapDataPreload[_currentIndex!].index!); + ref + .read(currentIndexProvider(chapter).notifier) + .setCurrentIndex(_uChapDataPreload[_currentIndex!].index!); + } } } diff --git a/lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart b/lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart index d5d85fb5..66b31f27 100644 --- a/lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart +++ b/lib/modules/manga/reader/widgets/btn_chapter_list_dialog.dart @@ -57,8 +57,8 @@ class _ChapterListWidgetState extends State { ); @override void initState() { - _jumpTo(); super.initState(); + _jumpTo(); } Future _jumpTo() async { diff --git a/lib/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart b/lib/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart index 3fe2de49..08c729ea 100644 --- a/lib/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart +++ b/lib/modules/manga/reader/widgets/circular_progress_indicator_animate_rotate.dart @@ -51,9 +51,13 @@ class _CircularProgressIndicatorAnimateRotateState duration: const Duration(milliseconds: 500), curve: Curves.easeInOut, tween: Tween(begin: 0, end: widget.progress), - builder: - (context, value, _) => - CircularProgressIndicator(value: value), + builder: (context, value, _) { + final safeValue = + value.isNaN || value.isInfinite + ? null + : value.clamp(0.0, 1.0); + return CircularProgressIndicator(value: safeValue); + }, ), ), ); diff --git a/lib/modules/more/categories/categories_screen.dart b/lib/modules/more/categories/categories_screen.dart index 3d07c1b2..29e7977c 100644 --- a/lib/modules/more/categories/categories_screen.dart +++ b/lib/modules/more/categories/categories_screen.dart @@ -27,10 +27,9 @@ class _CategoriesScreenState extends ConsumerState int tabs = 3; @override void initState() { + super.initState(); _tabBarController = TabController(length: tabs, vsync: this); _tabBarController.animateTo(widget.data.$2); - - super.initState(); } @override diff --git a/lib/modules/more/settings/appearance/providers/theme_provider.dart b/lib/modules/more/settings/appearance/providers/theme_provider.dart new file mode 100644 index 00000000..a5273051 --- /dev/null +++ b/lib/modules/more/settings/appearance/providers/theme_provider.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'blend_level_state_provider.dart'; +import 'flex_scheme_color_state_provider.dart'; +import 'pure_black_dark_mode_state_provider.dart'; +import 'app_font_family.dart'; + +/// Provides the light theme for the app, recomputed only when +/// flex scheme colors, blend level, or font family change. +final lightThemeProvider = Provider((ref) { + final colors = ref.watch(flexSchemeColorStateProvider); + final blendLevel = ref.watch(blendLevelStateProvider).toInt(); + final fontFamily = ref.watch(appFontFamilyProvider); + + return FlexThemeData.light( + colors: colors, + surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, + blendLevel: blendLevel, + appBarOpacity: 0.00, + subThemesData: const FlexSubThemesData( + blendOnLevel: 10, + thinBorderWidth: 2.0, + unselectedToggleIsColored: true, + inputDecoratorRadius: 24.0, + chipRadius: 24.0, + ), + useMaterial3ErrorColors: true, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + useMaterial3: true, + fontFamily: fontFamily, + ); +}); + +/// Provides the dark theme for the app, recomputed only when +/// flex scheme colors, blend level, font family, or pure-black toggle change. +final darkThemeProvider = Provider((ref) { + final colors = ref.watch(flexSchemeColorStateProvider); + final blendLevel = ref.watch(blendLevelStateProvider).toInt(); + final fontFamily = ref.watch(appFontFamilyProvider); + final pureBlack = ref.watch(pureBlackDarkModeStateProvider); + + return FlexThemeData.dark( + colors: colors, + surfaceMode: FlexSurfaceMode.level, + blendLevel: blendLevel, + appBarOpacity: 0.00, + scaffoldBackground: pureBlack ? Colors.black : null, + subThemesData: const FlexSubThemesData( + blendOnLevel: 10, + thinBorderWidth: 2.0, + unselectedToggleIsColored: true, + inputDecoratorRadius: 24.0, + chipRadius: 24.0, + ), + useMaterial3ErrorColors: true, + visualDensity: FlexColorScheme.comfortablePlatformDensity, + useMaterial3: true, + fontFamily: fontFamily, + ); +}); diff --git a/lib/modules/more/settings/track/manage_trackers/manage_trackers.dart b/lib/modules/more/settings/track/manage_trackers/manage_trackers.dart index 28cff105..f30bb0ff 100644 --- a/lib/modules/more/settings/track/manage_trackers/manage_trackers.dart +++ b/lib/modules/more/settings/track/manage_trackers/manage_trackers.dart @@ -19,10 +19,10 @@ class _ManageTrackersScreenState extends State { late List trackPreferences = []; @override void initState() { + super.initState(); trackPreferences = isar.trackPreferences.filter().syncIdIsNotNull().findAllSync(); // trackPreferences.insert(0, TrackPreference(syncId: -1)); - super.initState(); } @override diff --git a/lib/modules/more/settings/track/manage_trackers/tracking_detail.dart b/lib/modules/more/settings/track/manage_trackers/tracking_detail.dart index 30f9bfd4..65d5e6b1 100644 --- a/lib/modules/more/settings/track/manage_trackers/tracking_detail.dart +++ b/lib/modules/more/settings/track/manage_trackers/tracking_detail.dart @@ -22,10 +22,9 @@ class _TrackingDetailState extends State late TabController _tabBarController; @override void initState() { + super.initState(); _tabBarController = TabController(length: 2, vsync: this); _tabBarController.animateTo(0); - - super.initState(); } @override diff --git a/lib/modules/updates/updates_screen.dart b/lib/modules/updates/updates_screen.dart index bf6cbf01..660e908a 100644 --- a/lib/modules/updates/updates_screen.dart +++ b/lib/modules/updates/updates_screen.dart @@ -91,10 +91,10 @@ class _UpdatesScreenState extends ConsumerState @override void initState() { + super.initState(); _tabBarController = TabController(length: tabs, vsync: this); _tabBarController.animateTo(0); _tabBarController.addListener(tabListener); - super.initState(); } final _textEditingController = TextEditingController(); diff --git a/lib/modules/webview/webview.dart b/lib/modules/webview/webview.dart index d02123ed..18452000 100644 --- a/lib/modules/webview/webview.dart +++ b/lib/modules/webview/webview.dart @@ -28,6 +28,7 @@ class _MangaWebViewState extends ConsumerState { bool isNotWebviewWindow = false; @override void initState() { + super.initState(); if (Platform.isLinux || Platform.isWindows) { _runWebViewDesktop(); } else { @@ -35,7 +36,6 @@ class _MangaWebViewState extends ConsumerState { isNotWebviewWindow = true; }); } - super.initState(); } Webview? _desktopWebview; diff --git a/lib/modules/widgets/custom_draggable_tabbar.dart b/lib/modules/widgets/custom_draggable_tabbar.dart index debd5031..96328727 100644 --- a/lib/modules/widgets/custom_draggable_tabbar.dart +++ b/lib/modules/widgets/custom_draggable_tabbar.dart @@ -25,10 +25,10 @@ class _MeasureWidgetSizeState extends State { @override initState() { + super.initState(); WidgetsBinding.instance.addPostFrameCallback( (_) => widget.onCalculateSize(_key.currentContext?.size), ); - super.initState(); } @override diff --git a/lib/services/trackers/kitsu.dart b/lib/services/trackers/kitsu.dart index 35f38298..348c1e49 100644 --- a/lib/services/trackers/kitsu.dart +++ b/lib/services/trackers/kitsu.dart @@ -1,4 +1,3 @@ -import 'dart:developer'; import 'package:http_interceptor/http_interceptor.dart'; import 'package:intl/intl.dart'; import 'package:mangayomi/eval/model/m_bridge.dart'; @@ -26,18 +25,13 @@ class Kitsu extends _$Kitsu { final String _algoliaUrl = 'https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/'; final String _algoliaAppId = 'AWQO5J657S'; - final String _algoliaFilter = - '&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D'; - final String _algoliaFilterAnime = - '&facetFilters=%5B%22kind%3Aanime%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22episodeCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D'; + String _algoliaFilter(bool isManga) => + '&facetFilters=%5B%22kind%3A${isManga ? 'manga' : 'anime'}%22%5D' + '&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22' + '${isManga ? 'chapter' : 'episode'}Count%22%2C%22posterImage%22%2C%22' + 'startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D'; - String _mangaUrl(int id) { - return 'https://kitsu.io/manga/$id'; - } - - String _animeUrl(int id) { - return 'https://kitsu.io/anime/$id'; - } + String _mediaUrl(String type, int id) => 'https://kitsu.io/$type/$id'; @override void build({required int syncId, ItemType? itemType}) {} @@ -60,14 +54,14 @@ class Kitsu extends _$Kitsu { jsonDecode(await response.stream.bytesToString()) as Map; final aKOAuth = OAuth.fromJson(res); - final currenUser = await _getCurrentUser(aKOAuth.accessToken!); + final currentUser = await _getCurrentUser(aKOAuth.accessToken!); ref .read(tracksProvider(syncId: syncId).notifier) .login( TrackPreference( - username: currenUser.$1, + username: currentUser.$1, syncId: syncId, - prefs: jsonEncode({"ratingSystem": currenUser.$2}), + prefs: jsonEncode({"ratingSystem": currentUser.$2}), oAuth: jsonEncode(aKOAuth.toJson()), ), ); @@ -78,14 +72,14 @@ class Kitsu extends _$Kitsu { } } - Future addLibManga(Track track) async { + Future addLib(Track track, bool isManga) async { final userId = _getUserId(); - final accessToken = _getAccesToken(); + final accessToken = _getAccessToken(); var data = jsonEncode({ 'data': { 'type': 'libraryEntries', 'attributes': { - 'status': toKitsuStatusManga(track.status), + 'status': toKitsuStatus(track.status, isManga), 'progress': track.lastChapterRead, }, 'relationships': { @@ -93,7 +87,7 @@ class Kitsu extends _$Kitsu { 'data': {'id': userId, 'type': 'users'}, }, 'media': { - 'data': {'id': track.mediaId, 'type': 'manga'}, + 'data': {'id': track.mediaId, 'type': isManga ? 'manga' : 'anime'}, }, }, }, @@ -108,7 +102,7 @@ class Kitsu extends _$Kitsu { body: data, ); if (response.statusCode != 200) { - return await findLibManga(track); + return await findLibItem(track, true); } var jsonData = jsonDecode(response.body) as Map; @@ -116,52 +110,14 @@ class Kitsu extends _$Kitsu { return track; } - Future addLibAnime(Track track) async { - final userId = _getUserId(); - log(track.mediaId.toString()); - final accessToken = _getAccesToken(); - var data = jsonEncode({ - 'data': { - 'type': 'libraryEntries', - 'attributes': { - 'status': tokitsuStatusAnime(track.status), - 'progress': track.lastChapterRead, - }, - 'relationships': { - 'user': { - 'data': {'id': userId, 'type': 'users'}, - }, - 'media': { - 'data': {'id': track.mediaId, 'type': 'anime'}, - }, - }, - }, - }); - - var response = await http.post( - Uri.parse('${_baseUrl}library-entries'), - headers: { - 'Content-Type': 'application/vnd.api+json', - 'Authorization': 'Bearer $accessToken', - }, - body: data, - ); - if (response.statusCode != 200) { - return await findLibAnime(track); - } - var jsonData = jsonDecode(response.body) as Map; - track.libraryId = int.parse(jsonData['data']['id']); - return track; - } - - Future updateLibManga(Track track) async { - final accessToken = _getAccesToken(); + Future updateLib(Track track, bool isManga) async { + final accessToken = _getAccessToken(); final data = jsonEncode({ "data": { "type": "libraryEntries", - "id": track.mediaId, + "id": track.libraryId, "attributes": { - "status": toKitsuStatusManga(track.status), + "status": toKitsuStatus(track.status, isManga), "progress": track.lastChapterRead, "ratingTwenty": _toKitsuScore(track.score!), "startedAt": _convertDate(track.startedReadingDate!), @@ -171,7 +127,7 @@ class Kitsu extends _$Kitsu { }); await http.patch( - Uri.parse('$_baseUrl/library-entries/${track.mediaId}'), + Uri.parse('${_baseUrl}library-entries/${track.libraryId}'), headers: { "Content-Type": "application/vnd.api+json", 'Authorization': 'Bearer $accessToken', @@ -181,302 +137,119 @@ class Kitsu extends _$Kitsu { return track; } - Future updateLibAnime(Track track) async { - final accessToken = _getAccesToken(); - final data = jsonEncode({ - "data": { - "type": "libraryEntries", - "id": track.mediaId, - "attributes": { - "status": tokitsuStatusAnime(track.status), - "progress": track.lastChapterRead, - "ratingTwenty": _toKitsuScore(track.score!), - "startedAt": _convertDate(track.startedReadingDate!), - "finishedAt": _convertDate(track.finishedReadingDate!), - }, - }, - }); + Future> search(String search, bool isManga) async { + final accessToken = _getAccessToken(); - await http.patch( - Uri.parse('$_baseUrl/library-entries/${track.mediaId}'), + final url = Uri.parse(_algoliaKeyUrl); + final algoliaKeyResponse = await makeGetRequest(url, accessToken); + final key = json.decode(algoliaKeyResponse.body)["media"]["key"]; + final response = await http.post( + Uri.parse(_algoliaUrl), headers: { - "Content-Type": "application/vnd.api+json", + "Content-Type": "application/json", + "X-Algolia-Application-Id": _algoliaAppId, + "X-Algolia-API-Key": key, + }, + body: json.encode({ + 'params': + 'query=${Uri.encodeComponent(search)}${_algoliaFilter(isManga)}', + }), + ); + final data = json.decode(response.body); + + final entries = + List>.from( + data['hits'], + ).where((element) => element["subtype"] != "novel").toList(); + final totalChapter = isManga ? "chapterCount" : "episodeCount"; + return entries + .map( + (jsonRes) => TrackSearch( + libraryId: jsonRes['id'], + syncId: syncId, + trackingUrl: _mediaUrl(isManga ? 'manga' : 'anime', jsonRes['id']), + mediaId: jsonRes['id'], + summary: jsonRes['synopsis'] ?? "", + totalChapter: (jsonRes[totalChapter] ?? 0), + coverUrl: jsonRes['posterImage']['original'] ?? "", + title: jsonRes['canonicalTitle'], + startDate: "", + publishingType: (jsonRes["subtype"] ?? ""), + publishingStatus: + jsonRes['endDate'] == null ? "Publishing" : "Finished", + ), + ) + .toList(); + } + + Future findLibItem(Track track, bool isManga) async { + final type = isManga ? "manga" : "anime"; + final userId = _getUserId(); + final accessToken = _getAccessToken(); + + final url = Uri.parse( + '${_baseUrl}library-entries?filter[${type}_id]=${track.libraryId}&filter[user_id]=$userId&include=$type', + ); + Response response = await makeGetRequest(url, accessToken); + if (response.statusCode == 200) { + final parsed = parseTrackResponse(response, track, type); + if (parsed != null) return parsed; + } + return await getItem(track, type); + } + + Future makeGetRequest(Uri url, String accessToken) async { + final response = await http.get( + url, + headers: { + "Content-Type": "application/json", 'Authorization': 'Bearer $accessToken', }, - body: data, ); + return response; + } + + Future getItem(Track track, String type) async { + final accessToken = _getAccessToken(); + final url = Uri.parse( + '${_baseUrl}library-entries?filter[id]=${track.mediaId}&include=$type', + ); + Response response = await makeGetRequest(url, accessToken); + if (response.statusCode == 200) { + return parseTrackResponse(response, track, type); + } + return null; + } + + Track? parseTrackResponse(Response response, Track track, String type) { + final jsonResponse = jsonDecode(utf8.decode(response.bodyBytes)); + + final List data = jsonResponse['data']; + + if (data.isEmpty) return null; + + final obj = data[0]; + final attributes = obj["attributes"]; + final included = jsonResponse['included'][0]["attributes"]; + final id = int.parse(obj["id"]); + final totalChapter = type == 'manga' ? "chapterCount" : "episodeCount"; + track.mediaId = id; + track.libraryId = id; + track.syncId = syncId; + track.trackingUrl = _mediaUrl(type, id); + track.totalChapter = included[totalChapter] ?? 0; + track.status = getKitsuTrackStatus(attributes["status"], type); + track.score = ((attributes["ratingTwenty"] ?? 0) / 2).toInt(); + track.title = included["canonicalTitle"]; + track.lastChapterRead = attributes["progress"]; + track.startedReadingDate = _parseDate(attributes["startedAt"]); + track.finishedReadingDate = _parseDate(attributes["finishedAt"]); return track; } - Future> search(String search) async { - final accessToken = _getAccesToken(); - - final algoliaKeyResponse = await http.get( - Uri.parse(_algoliaKeyUrl), - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - final key = json.decode(algoliaKeyResponse.body)["media"]["key"]; - final response = await http.post( - Uri.parse(_algoliaUrl), - headers: { - "Content-Type": "application/json", - "X-Algolia-Application-Id": _algoliaAppId, - "X-Algolia-API-Key": key, - }, - body: json.encode({ - 'params': 'query=${Uri.encodeComponent(search)}$_algoliaFilter', - }), - ); - final data = json.decode(response.body); - - final entries = - List>.from( - data['hits'], - ).where((element) => element["subtype"] != "novel").toList(); - return entries - .map( - (jsonRes) => TrackSearch( - libraryId: jsonRes['id'], - syncId: syncId, - trackingUrl: _mangaUrl(jsonRes['id']), - mediaId: jsonRes['id'], - summary: jsonRes['synopsis'] ?? "", - totalChapter: jsonRes['chapterCount'] ?? 0, - coverUrl: jsonRes['posterImage']['original'] ?? "", - title: jsonRes['canonicalTitle'], - startDate: "", - publishingType: jsonRes["subtype"] ?? "s", - publishingStatus: - jsonRes['endDate'] == null ? "Publishing" : "Finished", - ), - ) - .toList(); - } - - Future> searchAnime(String search) async { - final accessToken = _getAccesToken(); - - final algoliaKeyResponse = await http.get( - Uri.parse(_algoliaKeyUrl), - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - final key = json.decode(algoliaKeyResponse.body)["media"]["key"]; - final response = await http.post( - Uri.parse(_algoliaUrl), - headers: { - "Content-Type": "application/json", - "X-Algolia-Application-Id": _algoliaAppId, - "X-Algolia-API-Key": key, - }, - body: json.encode({ - 'params': 'query=${Uri.encodeComponent(search)}$_algoliaFilterAnime', - }), - ); - final data = json.decode(response.body); - - final entries = - List>.from( - data['hits'], - ).where((element) => element["subtype"] != "novel").toList(); - return entries - .map( - (jsonRes) => TrackSearch( - libraryId: jsonRes['id'], - syncId: syncId, - trackingUrl: _animeUrl(jsonRes['id']), - mediaId: jsonRes['id'], - summary: jsonRes['synopsis'] ?? "", - totalChapter: jsonRes['episodeCount'] ?? 0, - coverUrl: jsonRes['posterImage']['original'] ?? "", - title: jsonRes['canonicalTitle'], - startDate: "", - publishingType: jsonRes["subtype"] ?? "", - publishingStatus: - jsonRes['endDate'] == null ? "Publishing" : "Finished", - ), - ) - .toList(); - } - - Future getManga(Track track) async { - final accessToken = _getAccesToken(); - final url = Uri.parse( - '${_baseUrl}library-entries?filter[id]=${track.mediaId}&include=manga', - ); - final response = await http.get( - url, - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - if (response.statusCode == 200) { - final jsonResponse = jsonDecode(response.body); - - final List data = jsonResponse['data']; - - if (data.isNotEmpty) { - final obj = data[0]; - track.mediaId = int.parse(obj["id"]); - track.libraryId = int.parse(obj["id"]); - track.syncId = syncId; - track.trackingUrl = _mangaUrl(int.parse(obj["id"])); - track.status = _getKitsuTrackStatusManga(obj["attributes"]["status"]); - track.title = - jsonResponse['included'][0]["attributes"]["canonicalTitle"]; - track.totalChapter = - jsonResponse['included'][0]["attributes"]["chapterCount"] ?? 0; - track.score = ((obj["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); - track.lastChapterRead = obj["attributes"]["progress"]; - track.startedReadingDate = _parseDate(obj["attributes"]["startedAt"]); - track.finishedReadingDate = _parseDate(obj["attributes"]["finishedAt"]); - return track; - } - } - return null; - } - - Future findLibManga(Track track) async { - final userId = _getUserId(); - final accessToken = _getAccesToken(); - final url = Uri.parse( - '${_baseUrl}library-entries?filter[manga_id]=${track.mediaId}&filter[user_id]=$userId&include=manga', - ); - final response = await http.get( - url, - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - if (response.statusCode == 200) { - final jsonResponse = jsonDecode(response.body); - - final List data = jsonResponse['data']; - - if (data.isNotEmpty) { - final obj = data[0]; - track.mediaId = int.parse(obj["id"]); - track.libraryId = int.parse(obj["id"]); - track.syncId = syncId; - track.trackingUrl = _mangaUrl(int.parse(obj["id"])); - track.title = - jsonResponse['included'][0]["attributes"]["canonicalTitle"]; - track.totalChapter = - jsonResponse['included'][0]["attributes"]["chapterCount"] ?? 0; - track.status = _getKitsuTrackStatusManga(obj["attributes"]["status"]); - track.score = ((obj["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); - track.lastChapterRead = obj["attributes"]["progress"]; - track.startedReadingDate = _parseDate(obj["attributes"]["startedAt"]); - track.finishedReadingDate = _parseDate(obj["attributes"]["finishedAt"]); - return track; - } - } - return await getManga(track); - } - - Future findLibAnime(Track track) async { - final userId = _getUserId(); - final accessToken = _getAccesToken(); - final url = Uri.parse( - '${_baseUrl}library-entries?filter[anime_id]=${track.mediaId}&filter[user_id]=$userId&include=anime', - ); - final response = await http.get( - url, - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - - if (response.statusCode == 200) { - final jsonResponse = jsonDecode(response.body); - - final List data = jsonResponse['data']; - if (data.isNotEmpty) { - track.mediaId = int.parse(data[0]["id"]); - track.libraryId = int.parse(data[0]["id"]); - track.syncId = syncId; - track.trackingUrl = _animeUrl(int.parse(data[0]["id"])); - track.status = _getKitsuTrsackStatusAnime( - data[0]["attributes"]["status"], - ); - track.title = - jsonResponse['included'][0]["attributes"]["canonicalTitle"]; - track.totalChapter = - jsonResponse['included'][0]["attributes"]["episodeCount"] ?? 0; - track.score = - ((data[0]["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); - track.lastChapterRead = data[0]["attributes"]["progress"]; - track.startedReadingDate = _parseDate( - data[0]["attributes"]["startedAt"], - ); - track.finishedReadingDate = _parseDate( - data[0]["attributes"]["finishedAt"], - ); - return track; - } - } - return await getAnime(track); - } - - Future getAnime(Track track) async { - final accessToken = _getAccesToken(); - final url = Uri.parse( - '${_baseUrl}library-entries?filter[id]=${track.mediaId}&include=anime', - ); - final response = await http.get( - url, - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); - if (response.statusCode == 200) { - final jsonResponse = jsonDecode(response.body); - - final List data = jsonResponse['data']; - if (data.isNotEmpty) { - track.mediaId = int.parse(data[0]["id"]); - track.libraryId = int.parse(data[0]["id"]); - track.syncId = syncId; - track.trackingUrl = _animeUrl(int.parse(data[0]["id"])); - track.status = _getKitsuTrsackStatusAnime( - data[0]["attributes"]["status"], - ); - track.score = - ((data[0]["attributes"]["ratingTwenty"] ?? 0) / 2).toInt(); - track.title = - jsonResponse['included'][0]["attributes"]["canonicalTitle"]; - track.totalChapter = - jsonResponse['included'][0]["attributes"]["episodeCount"] ?? 0; - track.lastChapterRead = data[0]["attributes"]["progress"]; - track.startedReadingDate = _parseDate( - data[0]["attributes"]["startedAt"], - ); - track.finishedReadingDate = _parseDate( - data[0]["attributes"]["finishedAt"], - ); - return track; - } - } - return null; - } - Future<(String, String)> _getCurrentUser(String accessToken) async { - final response = await http.get( - Uri.parse("${_baseUrl}users?filter[self]=true"), - headers: { - "Content-Type": "application/json", - 'Authorization': 'Bearer $accessToken', - }, - ); + final url = Uri.parse('${_baseUrl}users?filter[self]=true'); + Response response = await makeGetRequest(url, accessToken); final data = json.decode(response.body)['data'][0]; return ( data['id'].toString(), @@ -484,7 +257,7 @@ class Kitsu extends _$Kitsu { ); } - String _getAccesToken() { + String _getAccessToken() { final track = ref.watch(tracksProvider(syncId: syncId)); final mAKOAuth = OAuth.fromJson( jsonDecode(track!.oAuth!) as Map, @@ -503,54 +276,28 @@ class Kitsu extends _$Kitsu { return track!.username!; } - TrackStatus _getKitsuTrsackStatusAnime(String status) { + TrackStatus getKitsuTrackStatus(String status, String type) { return switch (status) { - "current" => TrackStatus.watching, + "current" => type == "manga" ? TrackStatus.reading : TrackStatus.watching, "completed" => TrackStatus.completed, "on_hold" => TrackStatus.onHold, "dropped" => TrackStatus.dropped, - _ => TrackStatus.planToWatch, + _ => type == "manga" ? TrackStatus.planToRead : TrackStatus.planToWatch, }; } - TrackStatus _getKitsuTrackStatusManga(String status) { - return switch (status) { - "current" => TrackStatus.reading, - "completed" => TrackStatus.completed, - "on_hold" => TrackStatus.onHold, - "dropped" => TrackStatus.dropped, - _ => TrackStatus.planToRead, - }; - } - - List kitsuStatusListManga = [ - TrackStatus.reading, + List kitsuStatusList(bool isManga) => [ + isManga ? TrackStatus.reading : TrackStatus.watching, TrackStatus.completed, TrackStatus.onHold, TrackStatus.dropped, - TrackStatus.planToRead, - ]; - List kitsuStatusListAnime = [ - TrackStatus.watching, - TrackStatus.completed, - TrackStatus.onHold, - TrackStatus.dropped, - TrackStatus.planToWatch, + isManga ? TrackStatus.planToRead : TrackStatus.planToWatch, ]; - String? toKitsuStatusManga(TrackStatus status) { + String? toKitsuStatus(TrackStatus status, bool isManga) { return switch (status) { - TrackStatus.reading => "current", - TrackStatus.completed => "completed", - TrackStatus.onHold => "on_hold", - TrackStatus.dropped => "dropped", - _ => "planned", - }; - } - - String? tokitsuStatusAnime(TrackStatus status) { - return switch (status) { - TrackStatus.watching => "current", + TrackStatus.reading when isManga => "current", + TrackStatus.watching when !isManga => "current", TrackStatus.completed => "completed", TrackStatus.onHold => "on_hold", TrackStatus.dropped => "dropped", diff --git a/pubspec.lock b/pubspec.lock index f6cffcd6..6029b86a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -479,6 +479,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.6" + fading_edge_scrollview: + dependency: transitive + description: + name: fading_edge_scrollview + sha256: "1f84fe3ea8e251d00d5735e27502a6a250e4aa3d3b330d3fdcb475af741464ef" + url: "https://pub.dev" + source: hosted + version: "4.1.1" fake_async: dependency: transitive description: @@ -1117,6 +1125,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3-main.0" + marquee: + dependency: "direct main" + description: + name: marquee + sha256: a87e7e80c5d21434f90ad92add9f820cf68be374b226404fe881d2bba7be0862 + url: "https://pub.dev" + source: hosted + version: "2.3.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d523b674..53644b1c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -81,6 +81,7 @@ dependencies: protobuf: ^3.1.0 device_info_plus: ^11.3.3 flutter_app_installer: ^1.0.0 + marquee: ^2.2.3 dependency_overrides: ffi: ^2.1.3