From d3c648f0b968971dcd94a83fbbf2723db0b6925d Mon Sep 17 00:00:00 2001 From: franzap <_@franzap.com> Date: Fri, 8 Nov 2024 22:35:00 -0300 Subject: [PATCH] - Ability to disable updates, show disabled updates app in updates screen, closes #157 - Better installation feedback messages (new plugin) - Bigger toasts and improved explanations, add toast actions, closes #159 - Add install success toast, closes #154 - Remove default Signed by zap.store - refreshUpdateStatus performance optimization for one app --- lib/models/app.dart | 76 +++++-- lib/models/local_app.dart | 89 ++++++-- lib/models/local_app.g.dart | 2 + lib/navigation/app_initializer.dart | 7 +- lib/screens/updates_screen.dart | 39 +++- lib/utils/extensions.dart | 45 +++- lib/widgets/app_card.dart | 3 +- lib/widgets/install_button.dart | 29 ++- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec.lock | 198 +++++++++++------- pubspec.yaml | 5 +- 11 files changed, 341 insertions(+), 154 deletions(-) diff --git a/lib/models/app.dart b/lib/models/app.dart index b731aae..5d80053 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -1,15 +1,15 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; -import 'dart:typed_data'; +import 'package:android_package_installer/android_package_installer.dart'; import 'package:android_package_manager/android_package_manager.dart'; import 'package:async/async.dart'; import 'package:collection/collection.dart'; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_data/flutter_data.dart'; -import 'package:install_plugin/install_plugin.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:purplebase/purplebase.dart'; @@ -21,6 +21,7 @@ import 'package:zapstore/models/release.dart'; import 'package:zapstore/models/user.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; +import 'package:zapstore/navigation/app_initializer.dart'; import 'package:zapstore/utils/extensions.dart'; import 'package:zapstore/utils/system_info.dart'; @@ -66,9 +67,10 @@ class App extends BaseApp with DataModelMixin { signer.isPresent; bool get isUpdated => localApp.value?.status == AppInstallStatus.updated; bool get isDowngrade => localApp.value?.status == AppInstallStatus.downgrade; + bool get isDisabled => localApp.value?.disabled == true; Future install() async { - if (!canInstall && !canUpdate) { + if (!canInstall && !canUpdate || isDisabled) { return; } @@ -80,9 +82,20 @@ class App extends BaseApp with DataModelMixin { final match = await packageCertificateMatches(); if (match == false) { notifier.state = ErrorInstallProgress( - Exception('Update is not possible'), + Exception('App failed security check'), info: - 'App failed a security check (APK cert mismatch). To fix, remove the app.', + '''There was an APK certificate mismatch for $name ($identifier). + +This could mean a malicious developer signed this update. + +If you want to update anyway, you need to manually uninstall the app and install it again. + +It could also mean the original developer signed with a different key. If you are sure you installed $name through another app store, you can choose to turn off zap.store updates. +''', + actions: [ + ('Close notice (recommended)', () async {}), + ('Disable $name updates', disableUpdates) + ], ); return; } @@ -116,18 +129,26 @@ class App extends BaseApp with DataModelMixin { } notifier.state = RequestInstallProgress(); - final result = await InstallPlugin.install(file.path); - - if (result['isSuccess']) { - await file.delete(); - await adapter.ref.localApps.localAppAdapter - .refreshUpdateStatus(appId: identifier); - } else { - const msg = 'App not installed'; - notifier.state = ErrorInstallProgress( - Exception(msg), - info: result['errorMessage'], - ); + final result = + await AndroidPackageInstaller.installApk(apkFilePath: file.path); + final installationStatus = + result != null ? PackageInstallerStatus.byCode(result) : null; + switch (installationStatus) { + case PackageInstallerStatus.success: + notifier.state = IdleInstallProgress(success: true); + await file.delete(); + await adapter.ref.localApps.localAppAdapter + .refreshUpdateStatus(appId: identifier); + break; + case PackageInstallerStatus.failureAborted: + // Simply reset state + notifier.state = IdleInstallProgress(); + break; + default: + notifier.state = ErrorInstallProgress( + Exception('Error installing app'), + info: installationStatus?.name, + ); } } @@ -199,8 +220,21 @@ class App extends BaseApp with DataModelMixin { return metadataSigHashes .any((msh) => msh.toLowerCase() == installedApkSigHash); } + + /// Disables updates to this app by putting the app identifier in local storage (shared prefs) + Future disableUpdates() async { + final adapter = DataModel.adapterFor(localApp.value!) as LocalAppAdapter; + final value = sharedPreferences!.getStringList(kDisabledUpdatesKey) ?? []; + if (!value.contains(identifier)) { + await sharedPreferences! + .setStringList(kDisabledUpdatesKey, [...value, identifier!]); + } + await adapter.refreshUpdateStatus(appId: identifier!); + } } +const kDisabledUpdatesKey = 'disabled_updates'; + mixin AppAdapter on Adapter { final queriedAtMap = {}; @@ -430,7 +464,10 @@ extension AppExt on Iterable { sealed class AppInstallProgress {} -class IdleInstallProgress extends AppInstallProgress {} +class IdleInstallProgress extends AppInstallProgress { + final bool? success; + IdleInstallProgress({this.success}); +} class DownloadingInstallProgress extends AppInstallProgress { final double progress; @@ -444,7 +481,8 @@ class RequestInstallProgress extends AppInstallProgress {} class ErrorInstallProgress extends AppInstallProgress { final Exception e; final String? info; - ErrorInstallProgress(this.e, {this.info}); + final List<(String, Future Function())> actions; + ErrorInstallProgress(this.e, {this.info, this.actions = const []}); } extension ExceptionExt on Exception { diff --git a/lib/models/local_app.dart b/lib/models/local_app.dart index c488032..49acbfa 100644 --- a/lib/models/local_app.dart +++ b/lib/models/local_app.dart @@ -6,6 +6,7 @@ import 'package:flutter_data/flutter_data.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:zapstore/main.data.dart'; import 'package:zapstore/models/app.dart'; +import 'package:zapstore/navigation/app_initializer.dart'; import 'package:zapstore/utils/system_info.dart'; part 'local_app.g.dart'; @@ -18,23 +19,26 @@ class LocalApp extends DataModel { final String? installedVersion; final int? installedVersionCode; final AppInstallStatus? status; - // final String? apkSignatureHash; + final bool disabled; LocalApp( {required this.id, this.installedVersion, this.installedVersionCode, - this.status}); + this.status, + this.disabled = false}); LocalApp copyWith( {String? installedVersion, int? installedVersionCode, - AppInstallStatus? status}) { + AppInstallStatus? status, + bool? disabled}) { return LocalApp( id: id, installedVersion: installedVersion ?? this.installedVersion, installedVersionCode: installedVersionCode ?? this.installedVersionCode, status: status ?? this.status, + disabled: disabled ?? this.disabled, ); } } @@ -51,35 +55,74 @@ mixin LocalAppAdapter on Adapter { final installedPackageInfos = infos!.where((i) => !kExcludedAppIdNamespaces.any((e) => i.packageName!.startsWith(e))); - final ids = appId != null - ? [appId] - : installedPackageInfos.map((i) => i.packageName).nonNulls; + final ids = installedPackageInfos.map((i) => i.packageName).nonNulls; + final disabledUpdatesIdentifiers = + sharedPreferences!.getStringList(kDisabledUpdatesKey) ?? []; - final localApps = findManyLocalByIds(ids); - final apps = ref.apps.appAdapter.findWhereIdentifierInLocal(ids); + // appId == null is the case of all apps + if (appId == null) { + final localApps = findAllLocal(); + final apps = ref.apps.appAdapter.findWhereIdentifierInLocal(ids); - for (final i in installedPackageInfos) { - final appId = i.packageName!; - final app = apps.firstWhereOrNull((a) => a.identifier == appId); + final localAppsToSave = []; - final installedVersion = i.versionName; - final installedVersionCode = i.versionCode; + for (final i in installedPackageInfos) { + final appId = i.packageName!; + final app = apps.firstWhereOrNull((a) => a.identifier == appId); + + final installedVersion = i.versionName; + final installedVersionCode = i.versionCode; + + final status = + determineUpdateStatus(app, installedVersion, installedVersionCode); + + if (status == null) { + continue; + } + final localApp = localApps.firstWhereOrNull((app) => appId == app.id) ?? + LocalApp(id: i.packageName!); + localAppsToSave.add( + localApp.copyWith( + installedVersion: installedVersion, + installedVersionCode: installedVersionCode, + status: status, + disabled: disabledUpdatesIdentifiers.contains(localApp.id), + ), + ); + } + + // Save local apps + await saveManyLocal(localAppsToSave, async: true); + + // Remove uninstalled apps + final uninstalledApps = localApps.where((a) => !ids.contains(a.id)); + if (uninstalledApps.isNotEmpty) { + deleteLocalByKeys(uninstalledApps.map((a) => DataModel.keyFor(a))); + } + } else { + final app = + ref.apps.appAdapter.findWhereIdentifierInLocal({appId}).firstOrNull; + final i = + installedPackageInfos.firstWhereOrNull((i) => i.packageName == appId); + + final installedVersion = i?.versionName; + final installedVersionCode = i?.versionCode; final status = determineUpdateStatus(app, installedVersion, installedVersionCode); - if (status == null) { - continue; - } + if (status != null) { + final localApp = app!.localApp.value ?? LocalApp(id: i!.packageName!); - final localApp = localApps.firstWhereOrNull((app) => appId == app.id) ?? - LocalApp(id: i.packageName!); - localApp - .copyWith( + localApp + .copyWith( installedVersion: installedVersion, installedVersionCode: installedVersionCode, - status: status) - .saveLocal(); + status: status, + disabled: disabledUpdatesIdentifiers.contains(localApp.id), + ) + .saveLocal(); + } } updateNumberOfApps(); @@ -87,7 +130,7 @@ mixin LocalAppAdapter on Adapter { void updateNumberOfApps() { final rs = db.select( - 'SELECT count(*) as c FROM localApps WHERE json_extract(data, \'\$.status\') is \'updatable\''); + 'SELECT count(*) as c FROM localApps WHERE json_extract(data, \'\$.status\') is \'updatable\' AND json_extract(data, \'\$.disabled\') = false'); ref.read(appsToUpdateProvider.notifier).state = rs.first['c']; } diff --git a/lib/models/local_app.g.dart b/lib/models/local_app.g.dart index ae78276..05c83c7 100644 --- a/lib/models/local_app.g.dart +++ b/lib/models/local_app.g.dart @@ -51,6 +51,7 @@ LocalApp _$LocalAppFromJson(Map json) => LocalApp( installedVersion: json['installedVersion'] as String?, installedVersionCode: (json['installedVersionCode'] as num?)?.toInt(), status: $enumDecodeNullable(_$AppInstallStatusEnumMap, json['status']), + disabled: json['disabled'] as bool? ?? false, ); Map _$LocalAppToJson(LocalApp instance) => { @@ -58,6 +59,7 @@ Map _$LocalAppToJson(LocalApp instance) => { 'installedVersion': instance.installedVersion, 'installedVersionCode': instance.installedVersionCode, 'status': _$AppInstallStatusEnumMap[instance.status], + 'disabled': instance.disabled, }; const _$AppInstallStatusEnumMap = { diff --git a/lib/navigation/app_initializer.dart b/lib/navigation/app_initializer.dart index 6c61a91..0849b6c 100644 --- a/lib/navigation/app_initializer.dart +++ b/lib/navigation/app_initializer.dart @@ -10,17 +10,18 @@ import 'package:zapstore/navigation/router.dart'; import 'package:zapstore/widgets/app_curation_container.dart'; AppLifecycleListener? _lifecycleListener; +SharedPreferences? sharedPreferences; final appInitializer = FutureProvider((ref) async { - final prefs = await SharedPreferences.getInstance(); - final dbVersion = prefs.getInt('dbVersion'); + sharedPreferences = await SharedPreferences.getInstance(); + final dbVersion = sharedPreferences!.getInt('dbVersion'); // Perform migration if (dbVersion == null || dbVersion < kDbVersion) { final storage = ref.read(localStorageProvider); await storage.initialize(); await storage.destroy(); - await prefs.setInt('dbVersion', kDbVersion); + await sharedPreferences!.setInt('dbVersion', kDbVersion); } // Initialize Flutter Data diff --git a/lib/screens/updates_screen.dart b/lib/screens/updates_screen.dart index 114e003..7119203 100644 --- a/lib/screens/updates_screen.dart +++ b/lib/screens/updates_screen.dart @@ -14,7 +14,8 @@ class UpdatesScreen extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(updatesProvider); - final (updatedApps, updatableApps) = state.value ?? ([], []); + final (updatedApps, updatableApps, updatesDisabledApps) = + state.value ?? ([], [], []); return SingleChildScrollView( physics: AlwaysScrollableScrollPhysics(), @@ -37,7 +38,9 @@ class UpdatesScreen extends HookConsumerWidget { children: [ Gap(20), Text( - '${updatableApps.length} update${updatableApps.length > 1 ? 's' : ''} available' + (updatableApps.length > 1 + ? '${updatableApps.length} updates available' + : 'One update available') .toUpperCase(), style: TextStyle( fontSize: 16, @@ -45,7 +48,6 @@ class UpdatesScreen extends HookConsumerWidget { fontWeight: FontWeight.w300, ), ), - // Gap(10), for (final app in updatableApps) AppCard(model: app, showUpdate: true), @@ -63,6 +65,19 @@ class UpdatesScreen extends HookConsumerWidget { ), Gap(10), for (final app in updatedApps) AppCard(model: app), + Gap(10), + if (updatesDisabledApps.isNotEmpty) + Text( + 'Disabled updates apps'.toUpperCase(), + style: TextStyle( + fontSize: 16, + letterSpacing: 3, + fontWeight: FontWeight.w300, + ), + ), + Gap(10), + for (final app in updatesDisabledApps) + AppCard(model: app, showUpdate: true), ], ), ); @@ -71,9 +86,9 @@ class UpdatesScreen extends HookConsumerWidget { // Provider -class UpdatesNotifier extends AsyncNotifier<(List, List)> { +class UpdatesNotifier extends AsyncNotifier<(List, List, List)> { @override - Future<(List, List)> build() async { + Future<(List, List, List)> build() async { // This provider is quite inefficient, as it watches everything // and always triggers as there's no proper equality for the return type // There is also no good way to manage state since returning a value @@ -83,11 +98,11 @@ class UpdatesNotifier extends AsyncNotifier<(List, List)> { final localAppsState = ref.localApps.watchAll(); if (!appsState.hasModel || !localAppsState.hasModel) { - return ([], []); + return ([], [], []); } final updatableApps = appsState.model - .where((app) => app.canUpdate) + .where((app) => app.canUpdate && !app.isDisabled) .toList() .sorted((a, b) => (a.name?.toLowerCase() ?? '') .compareTo(b.name?.toLowerCase() ?? '')); @@ -98,10 +113,16 @@ class UpdatesNotifier extends AsyncNotifier<(List, List)> { .sorted((a, b) => (a.name?.toLowerCase() ?? '') .compareTo(b.name?.toLowerCase() ?? '')); - return (updatedApps, updatableApps); + final updateDisabledApps = appsState.model + .where((app) => app.canUpdate && app.isDisabled) + .toList() + .sorted((a, b) => (a.name?.toLowerCase() ?? '') + .compareTo(b.name?.toLowerCase() ?? '')); + + return (updatedApps, updatableApps, updateDisabledApps); } } final updatesProvider = - AsyncNotifierProvider, List)>( + AsyncNotifierProvider, List, List)>( UpdatesNotifier.new); diff --git a/lib/utils/extensions.dart b/lib/utils/extensions.dart index f9f5400..5d6ac90 100644 --- a/lib/utils/extensions.dart +++ b/lib/utils/extensions.dart @@ -9,37 +9,61 @@ import 'package:zapstore/models/app.dart'; extension ContextX on BuildContext { ThemeData get theme => Theme.of(this); - void showInfo(String message, {Icon? icon}) { + void showInfo(String message, {String? description, Icon? icon}) { toastification.show( context: this, type: ToastificationType.info, icon: icon ?? Icon(Icons.info), style: ToastificationStyle.fillColored, + alignment: Alignment.topCenter, title: Text( message, - style: TextStyle(fontSize: 16), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), - autoCloseDuration: const Duration(seconds: 3), + description: description != null ? Text(description) : null, + autoCloseDuration: const Duration(seconds: 4), showProgressBar: false, closeOnClick: true, - alignment: Alignment.bottomCenter, ); } - void showError({required String title, String? description, Icon? icon}) { + void showError( + {required String title, + String? description, + Icon? icon, + List<(String, Future Function())> actions = const []}) { toastification.show( context: this, type: ToastificationType.error, style: ToastificationStyle.fillColored, + alignment: Alignment.topCenter, icon: icon ?? Icon(Icons.error), title: Text( title, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + style: TextStyle(fontSize: 17, fontWeight: FontWeight.bold), + overflow: TextOverflow.ellipsis, ), showProgressBar: false, closeOnClick: true, - description: description != null ? Text(description) : null, - alignment: Alignment.lerp(Alignment.bottomCenter, Alignment.center, 0.25), + description: description != null + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText(text: TextSpan(text: description)), + for (final (text, fn) in actions) + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.transparent), + onPressed: () async { + await fn.call(); + toastification.dismissAll(delayForAnimation: false); + }, + child: Text(text), + ), + ], + ) + : null, ); } @@ -75,11 +99,12 @@ HasMany hasMany>(Map map) { return HasMany.fromJson(map); } +final emojiParser = EmojiParser(); + extension StringWidget on String { - static final _emojiParser = EmojiParser(); String parseEmojis() { return replaceAllMapped(RegExp(':([a-z]*):'), (m) { - return _emojiParser.hasName(m[1]!) ? _emojiParser.get(m[1]!).code : m[0]!; + return emojiParser.hasName(m[1]!) ? emojiParser.get(m[1]!).code : m[0]!; }); } diff --git a/lib/widgets/app_card.dart b/lib/widgets/app_card.dart index f905a37..30c6553 100644 --- a/lib/widgets/app_card.dart +++ b/lib/widgets/app_card.dart @@ -83,7 +83,8 @@ class AppCard extends HookConsumerWidget { ), Gap(6), if (app.signer.isPresent && - app.signer.value!.pubkey != kZapstorePubkey) + (app.signer.value!.pubkey != kZapstorePubkey || + {'store.zap.app'}.contains(app.identifier))) AuthorContainer( user: app.signer.value!, text: 'Signed by', diff --git a/lib/widgets/install_button.dart b/lib/widgets/install_button.dart index b013c0d..4e9f76c 100644 --- a/lib/widgets/install_button.dart +++ b/lib/widgets/install_button.dart @@ -1,5 +1,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:percent_indicator/linear_percent_indicator.dart'; @@ -15,7 +16,7 @@ import 'package:zapstore/widgets/author_container.dart'; import 'package:zapstore/widgets/spinning_logo.dart'; import 'package:zapstore/widgets/wot_container.dart'; -class InstallButton extends ConsumerWidget { +class InstallButton extends HookConsumerWidget { InstallButton({ super.key, required this.app, @@ -30,6 +31,16 @@ class InstallButton extends ConsumerWidget { final progress = ref.watch(installationProgressProvider(app.id!)); final status = app.localApp.value?.status; + useMemoized(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (progress is IdleInstallProgress && progress.success == true) { + context.showInfo('Success', + description: + '${app.name ?? app.identifier} was successfully installed'); + } + }); + }, [app.id, progress]); + return GestureDetector( onTap: switch (status) { AppInstallStatus.downgrade => null, @@ -47,15 +58,18 @@ class InstallButton extends ConsumerWidget { ); } else if (app.canUpdate) { app.install(); + } else if (app.isDisabled) { + // nothing } else { context.showError( - title: 'Can\'t install', + title: 'Installation not possible', description: 'Release or signer are missing.'); } }, - ErrorInstallProgress(:final e, :final info) => () { + ErrorInstallProgress(:final e, :final info, :final actions) => () { // show error and reset state to idle - context.showError(title: e.message, description: info); + context.showError( + title: e.message, description: info, actions: actions); ref.read(installationProgressProvider(app.id!).notifier).state = IdleInstallProgress(); }, @@ -74,6 +88,7 @@ class InstallButton extends ConsumerWidget { }, backgroundColor: switch (progress) { ErrorInstallProgress() => Colors.red, + IdleInstallProgress() => app.isDisabled ? Colors.grey : kUpdateColor, _ => kUpdateColor, }, progressColor: Colors.blue[800], @@ -91,7 +106,7 @@ class InstallButton extends ConsumerWidget { ? Padding( padding: const EdgeInsets.only(left: 8, right: 8), child: AutoSizeText( - 'Update', + app.isDisabled ? 'Disabled updates' : 'Update', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( @@ -109,10 +124,10 @@ class InstallButton extends ConsumerWidget { VerifyingHashProgress() => Text('Verifying file integrity'), RequestInstallProgress() => Text('Requesting ${app.canUpdate ? 'update' : 'installation'}'), - ErrorInstallProgress(:final e) => Padding( + ErrorInstallProgress() => Padding( padding: const EdgeInsets.only(left: 8, right: 8), child: Text( - '${e.message.substringMax(64)} (tap for more)', + 'Error (tap for details)', textAlign: TextAlign.center, ), ), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b2ca7fe..2060261 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,7 +9,7 @@ import app_links import device_info_plus import path_provider_foundation import shared_preferences_foundation -import sqflite +import sqflite_darwin import sqlite3_flutter_libs import url_launcher_macos diff --git a/pubspec.lock b/pubspec.lock index 32e7b70..dcaecf0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,32 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.7.0" + android_package_installer: + dependency: "direct main" + description: + path: "." + ref: ba2aa7a + resolved-ref: ba2aa7a11edc2649d1d80c25ed9291521262f714 + url: "https://github.com/zapstore/android_package_installer" + source: git + version: "0.0.1" android_package_manager: dependency: "direct main" description: @@ -69,10 +83,10 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: "direct main" description: @@ -165,18 +179,18 @@ packages: dependency: "direct main" description: name: build_runner - sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.13" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.3.1" + version: "7.3.2" built_collection: dependency: transitive description: @@ -237,10 +251,10 @@ packages: dependency: transitive description: name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" clock: dependency: transitive description: @@ -253,10 +267,10 @@ packages: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: "direct main" description: @@ -269,18 +283,18 @@ packages: dependency: "direct main" description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" crypto: dependency: "direct main" description: name: crypto - sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" cupertino_icons: dependency: "direct main" description: @@ -301,10 +315,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" decimal: dependency: transitive description: @@ -365,18 +379,18 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -435,10 +449,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: e17575ca576a34b46c58c91f9948891117a1bd97815d2e661813c7f90c647a78 + sha256: f0e599ba89c9946c8e051780f0ec99aba4ba15895e0380a7ab68f420046fc44e url: "https://pub.dev" source: hosted - version: "0.7.3+2" + version: "0.7.4+1" flutter_phoenix: dependency: "direct main" description: @@ -451,10 +465,10 @@ packages: dependency: transitive description: name: flutter_riverpod - sha256: "711d916456563f715bde1e139d7cfdca009f8264befab3ac9f8ded8b6ec26405" + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.6.1" flutter_test: dependency: "direct dev" description: flutter @@ -509,10 +523,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc" + sha256: ce89c5a993ca5eea74535f798478502c30a625ecb10a1de4d7fef5cd1bcac2a4 url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.4.1" graphs: dependency: transitive description: @@ -533,10 +547,10 @@ packages: dependency: "direct main" description: name: hooks_riverpod - sha256: "534ca8af331235f700f3916a74e4579cdfdd708e2fbed342fc8aa969d41a7af0" + sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.6.1" http: dependency: "direct main" description: @@ -581,10 +595,10 @@ packages: dependency: transitive description: name: image - sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.3.0" inflection3: dependency: transitive description: @@ -593,14 +607,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.3+2" - install_plugin: - dependency: "direct main" - description: - name: install_plugin - sha256: "6fb67ba0781e75de4f2f2266ed25e835bfd277c5bfc2ed034af52774355857c6" - url: "https://pub.dev" - source: hosted - version: "2.1.0" intl: dependency: "direct main" description: @@ -677,10 +683,18 @@ packages: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" markdown: dependency: transitive description: @@ -717,10 +731,10 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" octo_image: dependency: transitive description: @@ -749,18 +763,18 @@ packages: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a url: "https://pub.dev" source: hosted - version: "2.2.10" + version: "2.2.12" path_provider_foundation: dependency: transitive description: @@ -821,10 +835,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa" + sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" url: "https://pub.dev" source: hosted - version: "12.0.12" + version: "12.0.13" permission_handler_apple: dependency: transitive description: @@ -869,10 +883,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -948,10 +962,10 @@ packages: dependency: transitive description: name: riverpod - sha256: c86fedfb45dd1da98ee6493dd9374325cdf494e7d523ebfb0c387eecc5f7b5c9 + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.6.1" rxdart: dependency: transitive description: @@ -972,10 +986,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + sha256: "3b9febd815c9ca29c9e3520d50ec32f49157711e143b7a4ca039eb87e8ade5ab" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3" shared_preferences_foundation: dependency: transitive description: @@ -1081,34 +1095,58 @@ packages: dependency: transitive description: name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" url: "https://pub.dev" source: hosted - version: "2.3.3+1" + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.4+5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" sqlite3: dependency: transitive description: name: sqlite3 - sha256: "45f168ae2213201b54e09429ed0c593dc2c88c924a1488d6f9c523a255d567cb" + sha256: bb174b3ec2527f9c5f680f73a89af8149dd99782fbb56ea88ad0807c5638f2ed url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.7" sqlite3_flutter_libs: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: "62bbb4073edbcdf53f40c80775f33eea01d301b7b81417e5b3fb7395416258c1" + sha256: "7ae52b23366e5295005022e62fa093f64bfe190810223ea0ebf733a4cd140bce" url: "https://pub.dev" source: hosted - version: "0.5.24" + version: "0.5.26" stack_trace: dependency: transitive description: @@ -1153,10 +1191,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" url: "https://pub.dev" source: hosted - version: "3.1.0+1" + version: "3.3.0+3" system_info2: dependency: "direct main" description: @@ -1201,26 +1239,26 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.14" url_launcher_ios: dependency: transitive description: @@ -1265,10 +1303,10 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" uuid: dependency: transitive description: @@ -1337,10 +1375,10 @@ packages: dependency: "direct overridden" description: name: win32 - sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + sha256: "84ba388638ed7a8cb3445a320c8273136ab2631cd5f2c57888335504ddab1bc2" url: "https://pub.dev" source: hosted - version: "5.5.4" + version: "5.8.0" win32_registry: dependency: transitive description: @@ -1374,5 +1412,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0c5093f..952c193 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,7 +47,10 @@ dependencies: app_links: ^6.3.2 dart_emoji: ^0.2.0+2 remove_markdown: ^0.0.2 - install_plugin: ^2.1.0 + android_package_installer: + git: + url: https://github.com/zapstore/android_package_installer + ref: ba2aa7a shared_preferences: ^2.3.2 dependency_overrides: