- 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
This commit is contained in:
franzap
2024-11-08 22:35:00 -03:00
parent 1c2831a1b2
commit d3c648f0b9
11 changed files with 341 additions and 154 deletions
+57 -19
View File
@@ -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<App> {
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<void> install() async {
if (!canInstall && !canUpdate) {
if (!canInstall && !canUpdate || isDisabled) {
return;
}
@@ -80,9 +82,20 @@ class App extends BaseApp with DataModelMixin<App> {
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<App> {
}
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<App> {
return metadataSigHashes
.any((msh) => msh.toLowerCase() == installedApkSigHash);
}
/// Disables updates to this app by putting the app identifier in local storage (shared prefs)
Future<void> 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<App> {
final queriedAtMap = <String, DateTime>{};
@@ -430,7 +464,10 @@ extension AppExt on Iterable<App> {
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<void> Function())> actions;
ErrorInstallProgress(this.e, {this.info, this.actions = const []});
}
extension ExceptionExt on Exception {
+66 -23
View File
@@ -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<LocalApp> {
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<LocalApp> {
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 = <LocalApp>[];
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<LocalApp> {
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'];
}
+2
View File
@@ -51,6 +51,7 @@ LocalApp _$LocalAppFromJson(Map<String, dynamic> 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<String, dynamic> _$LocalAppToJson(LocalApp instance) => <String, dynamic>{
@@ -58,6 +59,7 @@ Map<String, dynamic> _$LocalAppToJson(LocalApp instance) => <String, dynamic>{
'installedVersion': instance.installedVersion,
'installedVersionCode': instance.installedVersionCode,
'status': _$AppInstallStatusEnumMap[instance.status],
'disabled': instance.disabled,
};
const _$AppInstallStatusEnumMap = {
+4 -3
View File
@@ -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<void>((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
+30 -9
View File
@@ -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<App>, List<App>)> {
class UpdatesNotifier extends AsyncNotifier<(List<App>, List<App>, List<App>)> {
@override
Future<(List<App>, List<App>)> build() async {
Future<(List<App>, List<App>, List<App>)> 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<App>, List<App>)> {
final localAppsState = ref.localApps.watchAll();
if (!appsState.hasModel || !localAppsState.hasModel) {
return (<App>[], <App>[]);
return (<App>[], <App>[], <App>[]);
}
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<App>, List<App>)> {
.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<UpdatesNotifier, (List<App>, List<App>)>(
AsyncNotifierProvider<UpdatesNotifier, (List<App>, List<App>, List<App>)>(
UpdatesNotifier.new);
+35 -10
View File
@@ -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<void> 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<T> hasMany<T extends DataModelMixin<T>>(Map<String, dynamic> map) {
return HasMany<T>.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]!;
});
}
+2 -1
View File
@@ -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',
+22 -7
View File
@@ -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,
),
),
@@ -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
+118 -80
View File
@@ -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"
+4 -1
View File
@@ -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: