From 27690975f450addf87cd2981c33815dc7afdc375 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:24:23 +0200 Subject: [PATCH 1/2] Make `_updateLibrary()` fully async also make _updateNumbers() readable. --- lib/modules/updates/updates_screen.dart | 66 ++++++++++++------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/lib/modules/updates/updates_screen.dart b/lib/modules/updates/updates_screen.dart index 8eadbc37..3294b50d 100644 --- a/lib/modules/updates/updates_screen.dart +++ b/lib/modules/updates/updates_screen.dart @@ -98,24 +98,26 @@ class _UpdatesScreenState extends BaseLibraryTabScreenState { } Future _updateLibrary() async { - setState(() => _isLoading = true); - final itemType = getCurrentItemType(); - final mangaList = isar.mangas - .filter() - .idIsNotNull() - .favoriteEqualTo(true) - .and() - .itemTypeEqualTo(itemType) - .and() - .isLocalArchiveEqualTo(false) - .findAllSync(); - await updateLibrary( - ref: ref, - context: context, - mangaList: mangaList, - itemType: itemType, - ); - setState(() => _isLoading = false); + try { + setState(() => _isLoading = true); + final itemType = getCurrentItemType(); + final mangaList = await isar.mangas + .filter() + .idIsNotNull() + .favoriteEqualTo(true) + .itemTypeEqualTo(itemType) + .isLocalArchiveEqualTo(false) + .findAll(); + if (!mounted) return; + await updateLibrary( + ref: ref, + context: context, + mangaList: mangaList, + itemType: itemType, + ); + } finally { + if (mounted) setState(() => _isLoading = false); + } } Future _clearUpdates() async { @@ -124,6 +126,7 @@ class _UpdatesScreenState extends BaseLibraryTabScreenState { .idIsNotNull() .chapter((q) => q.manga((q) => q.itemTypeEqualTo(getCurrentItemType()))) .findAll(); + if (updates.isEmpty) return; final idsToDelete = []; isar.writeTxnSync(() { for (var update in updates) { @@ -133,7 +136,7 @@ class _UpdatesScreenState extends BaseLibraryTabScreenState { .addChangedPart(ActionType.removeUpdate, update.id, "{}", false); } }); - await isar.writeTxn(() => isar.updates.deleteAll(idsToDelete)); + await isar.writeTxn(() async => await isar.updates.deleteAll(idsToDelete)); } } @@ -273,25 +276,18 @@ Widget _updateNumbers(WidgetRef ref, ItemType itemType) { stream: isar.updates .filter() .idIsNotNull() - .and() .chapter((q) => q.manga((q) => q.itemTypeEqualTo(itemType))) .watch(fireImmediately: true), builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data!.isNotEmpty) { - final entries = snapshot.data!.toList(); - return entries.isEmpty - ? SizedBox.shrink() - : Badge( - backgroundColor: Theme.of(context).focusColor, - label: Text( - entries.length.toString(), - style: TextStyle( - color: Theme.of(context).textTheme.bodySmall!.color, - ), - ), - ); - } - return Container(); + final count = snapshot.data?.length ?? 0; + if (count == 0) return const SizedBox.shrink(); + return Badge( + backgroundColor: Theme.of(context).focusColor, + label: Text( + count.toString(), + style: TextStyle(color: Theme.of(context).textTheme.bodySmall!.color), + ), + ); }, ); } From 1b630954b1a3276c0694619d998d44094197d7fd Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:29:18 +0200 Subject: [PATCH 2/2] Remove BuildContext from checkForUpdate Provider Do not pass Context into any provider. Handle logic and BotToasts in the onTap of the AboutScreen, not inside the provider. Also make _checkUpdate() more efficient, by fetching the `/latest` API. Before, it was fetching 10 releases and immediately discarding 9, only leaving the latest. --- lib/modules/main_view/main_screen.dart | 13 ++- lib/modules/more/about/about_screen.dart | 36 ++++-- .../about/providers/check_for_update.dart | 94 ++++++---------- .../about/providers/check_for_update.g.dart | 103 +++++++----------- 4 files changed, 114 insertions(+), 132 deletions(-) diff --git a/lib/modules/main_view/main_screen.dart b/lib/modules/main_view/main_screen.dart index e84a64da..e4e872fa 100644 --- a/lib/modules/main_view/main_screen.dart +++ b/lib/modules/main_view/main_screen.dart @@ -11,6 +11,7 @@ import 'package:mangayomi/models/chapter.dart'; import 'package:mangayomi/models/manga.dart'; import 'package:mangayomi/models/update.dart'; import 'package:mangayomi/models/source.dart'; +import 'package:mangayomi/modules/more/about/providers/download_file_screen.dart'; import 'package:mangayomi/modules/more/providers/downloaded_only_state_provider.dart'; import 'package:mangayomi/modules/more/settings/reader/providers/reader_state_provider.dart'; import 'package:mangayomi/modules/more/settings/sync/providers/sync_providers.dart'; @@ -118,7 +119,6 @@ class _MainScreenState extends ConsumerState { void _initializeProviders() { Future.microtask(() { if (mounted) { - ref.read(checkForUpdateProvider(context: context)); for (var type in ItemType.values) { ref.read( fetchItemSourcesListProvider( @@ -169,6 +169,17 @@ class _MainScreenState extends ConsumerState { bool isLibSwitch = false; @override Widget build(BuildContext context) { + ref.listen>(checkForUpdateProvider, (_, next) { + next.whenData((updateInfo) { + if (updateInfo != null && context.mounted) { + showDialog( + context: context, + builder: (_) => DownloadFileScreen(updateAvailable: updateInfo), + ); + } + }); + }); + ref.listen(l10nLocaleStateProvider, (previous, next) { _clearCache(); setState(() {}); diff --git a/lib/modules/more/about/about_screen.dart b/lib/modules/more/about/about_screen.dart index 7ebde1ec..f4b81200 100644 --- a/lib/modules/more/about/about_screen.dart +++ b/lib/modules/more/about/about_screen.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -8,6 +9,7 @@ import 'package:mangayomi/eval/model/m_bridge.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/settings.dart'; import 'package:mangayomi/modules/more/about/providers/check_for_update.dart'; +import 'package:mangayomi/modules/more/about/providers/download_file_screen.dart'; import 'package:mangayomi/modules/more/about/providers/get_package_info.dart'; import 'package:mangayomi/modules/more/about/providers/logs_state.dart'; import 'package:mangayomi/modules/widgets/progress_center.dart'; @@ -73,13 +75,33 @@ class AboutScreen extends ConsumerWidget { }, ), ListTile( - onTap: () { - ref.read( - checkForUpdateProvider( - context: context, - manualUpdate: true, - ), - ); + onTap: () async { + BotToast.showText(text: l10n.searching_for_updates); + try { + final updateInfo = await performManualUpdateCheck(); + if (updateInfo != null) { + BotToast.showText( + text: l10n.new_update_available, + ); + await Future.delayed(const Duration(seconds: 1)); + if (context.mounted) { + showDialog( + context: context, + builder: (_) => DownloadFileScreen( + updateAvailable: updateInfo, + ), + ); + } + } else { + BotToast.showText( + text: l10n.no_new_updates_available, + ); + } + } catch (_) { + BotToast.showText( + text: l10n.no_new_updates_available, + ); + } }, title: Text(l10n.check_for_update), ), diff --git a/lib/modules/more/about/providers/check_for_update.dart b/lib/modules/more/about/providers/check_for_update.dart index 1f80d49d..ad3e09a5 100644 --- a/lib/modules/more/about/providers/check_for_update.dart +++ b/lib/modules/more/about/providers/check_for_update.dart @@ -1,12 +1,8 @@ import 'dart:convert'; import 'dart:developer'; -import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; import 'package:mangayomi/main.dart'; import 'package:mangayomi/models/settings.dart'; -import 'package:mangayomi/modules/more/about/providers/download_file_screen.dart'; -import 'package:mangayomi/providers/l10n_providers.dart'; import 'package:mangayomi/services/fetch_sources_list.dart'; import 'package:mangayomi/services/http/m_client.dart'; import 'package:mangayomi/utils/extensions/string_extensions.dart'; @@ -14,43 +10,29 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'check_for_update.g.dart'; -@riverpod -Future checkForUpdate( - Ref ref, { - BuildContext? context, - bool? manualUpdate, -}) async { - manualUpdate = manualUpdate ?? false; - final checkForUpdates = ref.read(checkForAppUpdatesProvider); - if (!checkForUpdates && !manualUpdate) return; - final l10n = l10nLocalizations(context!)!; +/// Convenience alias: (version, body, htmlUrl, assets). +typedef UpdateInfo = (String, String, String, List); - if (manualUpdate) { - BotToast.showText(text: l10n.searching_for_updates); - } +/// Automatic update-check provider. +/// +/// Respects the user's [checkForAppUpdatesProvider] preference. Returns +/// [UpdateInfo] when a newer version exists, `null` otherwise. +@riverpod +Future checkForUpdate(Ref ref) async { + if (!ref.read(checkForAppUpdatesProvider)) return null; + return _getUpdateIfAvailable(); +} + +/// Compares the running version against the latest release. +/// Returns [UpdateInfo] when an update is available, or `null` when already +/// up-to-date. Throws if the network request fails. +Future _getUpdateIfAvailable() async { final info = await PackageInfo.fromPlatform(); if (kDebugMode) { log(info.data.toString()); } - final updateAvailable = await _checkUpdate(); - if (compareVersions(info.version, updateAvailable.$1) < 0) { - if (manualUpdate) { - BotToast.showText(text: l10n.new_update_available); - await Future.delayed(const Duration(seconds: 1)); - } - if (context.mounted) { - showDialog( - context: context, - builder: (context) { - return DownloadFileScreen(updateAvailable: updateAvailable); - }, - ); - } - } else if (compareVersions(info.version, updateAvailable.$1) == 0) { - if (manualUpdate) { - BotToast.showText(text: l10n.no_new_updates_available); - } - } + final latest = await _fetchLatestRelease(); + return compareVersions(info.version, latest.$1) < 0 ? latest : null; } @riverpod @@ -58,27 +40,23 @@ bool checkForAppUpdates(Ref ref) { return isar.settings.getSync(227)?.checkForAppUpdates ?? true; } -Future<(String, String, String, List)> _checkUpdate() async { +/// Performs an update check unconditionally, ignoring the auto-update setting. +Future performManualUpdateCheck() => _getUpdateIfAvailable(); + +Future _fetchLatestRelease() async { final http = MClient.init(reqcopyWith: {'useDartHttpClient': true}); - try { - final res = await http.get( - Uri.parse( - "https://api.github.com/repos/kodjodevf/Mangayomi/releases?page=1&per_page=10", - ), - ); - List resListJson = jsonDecode(res.body) as List; - return ( - resListJson.first["name"] - .toString() - .substringAfter('v') - .substringBefore('-'), - resListJson.first["body"].toString(), - resListJson.first["html_url"].toString(), - (resListJson.first["assets"] as List) - .map((asset) => asset["browser_download_url"]) - .toList(), - ); - } catch (e) { - rethrow; - } + final res = await http.get( + Uri.parse( + 'https://api.github.com/repos/kodjodevf/Mangayomi/releases/latest', + ), + ); + final release = jsonDecode(res.body) as Map; + return ( + release['name'].toString().substringAfter('v').substringBefore('-'), + release['body'].toString(), + release['html_url'].toString(), + (release['assets'] as List) + .map((asset) => asset['browser_download_url']) + .toList(), + ); } diff --git a/lib/modules/more/about/providers/check_for_update.g.dart b/lib/modules/more/about/providers/check_for_update.g.dart index f60fc425..4df5080b 100644 --- a/lib/modules/more/about/providers/check_for_update.g.dart +++ b/lib/modules/more/about/providers/check_for_update.g.dart @@ -8,87 +8,58 @@ part of 'check_for_update.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint, type=warning +/// Automatic update-check provider. +/// +/// Respects the user's [checkForAppUpdatesProvider] preference. Returns +/// [UpdateInfo] when a newer version exists, `null` otherwise. @ProviderFor(checkForUpdate) -final checkForUpdateProvider = CheckForUpdateFamily._(); +final checkForUpdateProvider = CheckForUpdateProvider._(); + +/// Automatic update-check provider. +/// +/// Respects the user's [checkForAppUpdatesProvider] preference. Returns +/// [UpdateInfo] when a newer version exists, `null` otherwise. final class CheckForUpdateProvider - extends $FunctionalProvider, void, FutureOr> - with $FutureModifier, $FutureProvider { - CheckForUpdateProvider._({ - required CheckForUpdateFamily super.from, - required ({BuildContext? context, bool? manualUpdate}) super.argument, - }) : super( - retry: null, - name: r'checkForUpdateProvider', - isAutoDispose: true, - dependencies: null, - $allTransitiveDependencies: null, - ); + extends + $FunctionalProvider< + AsyncValue, + UpdateInfo?, + FutureOr + > + with $FutureModifier, $FutureProvider { + /// Automatic update-check provider. + /// + /// Respects the user's [checkForAppUpdatesProvider] preference. Returns + /// [UpdateInfo] when a newer version exists, `null` otherwise. + CheckForUpdateProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'checkForUpdateProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); @override String debugGetCreateSourceHash() => _$checkForUpdateHash(); - @override - String toString() { - return r'checkForUpdateProvider' - '' - '$argument'; - } - @$internal @override - $FutureProviderElement $createElement($ProviderPointer pointer) => - $FutureProviderElement(pointer); + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); @override - FutureOr create(Ref ref) { - final argument = - this.argument as ({BuildContext? context, bool? manualUpdate}); - return checkForUpdate( - ref, - context: argument.context, - manualUpdate: argument.manualUpdate, - ); - } - - @override - bool operator ==(Object other) { - return other is CheckForUpdateProvider && other.argument == argument; - } - - @override - int get hashCode { - return argument.hashCode; + FutureOr create(Ref ref) { + return checkForUpdate(ref); } } -String _$checkForUpdateHash() => r'8064f08768f4d98997201a2f07ba160c721a6a3f'; - -final class CheckForUpdateFamily extends $Family - with - $FunctionalFamilyOverride< - FutureOr, - ({BuildContext? context, bool? manualUpdate}) - > { - CheckForUpdateFamily._() - : super( - retry: null, - name: r'checkForUpdateProvider', - dependencies: null, - $allTransitiveDependencies: null, - isAutoDispose: true, - ); - - CheckForUpdateProvider call({BuildContext? context, bool? manualUpdate}) => - CheckForUpdateProvider._( - argument: (context: context, manualUpdate: manualUpdate), - from: this, - ); - - @override - String toString() => r'checkForUpdateProvider'; -} +String _$checkForUpdateHash() => r'7134bb3c6ac01bc16fecf975195a5231d57c6148'; @ProviderFor(checkForAppUpdates) final checkForAppUpdatesProvider = CheckForAppUpdatesProvider._();