Remember trusted signers, closes #141

This commit is contained in:
franzap
2024-12-07 15:02:02 -03:00
parent c91b7b53c6
commit 203f8b3df1
13 changed files with 199 additions and 122 deletions
+1
View File
@@ -10,6 +10,7 @@
analyzer:
errors:
no_leading_underscores_for_local_identifiers: ignore
must_be_immutable: ignore
include: package:flutter_lints/flutter.yaml
linter:
+8 -1
View File
@@ -69,7 +69,7 @@ class App extends BaseApp with DataModelMixin<App> {
bool get hasCertificateMismatch =>
localApp.value?.status == AppInstallStatus.certificateMismatch;
Future<void> install() async {
Future<void> install({bool alwaysTrustSigner = false}) async {
if (!canInstall && !canUpdate || hasCertificateMismatch) {
return;
}
@@ -78,6 +78,13 @@ class App extends BaseApp with DataModelMixin<App> {
final notifier =
adapter.ref.read(installationProgressProvider(id!).notifier);
// Persist trusted preference
if (alwaysTrustSigner) {
final settings = adapter.ref.settings.findOneLocalById('_')!;
settings.trustedUsers.add(signer.value!);
settings.saveLocal();
}
final installPermission = await Permission.requestInstallPackages.status;
if (!installPermission.isGranted) {
final newStatus = await Permission.requestInstallPackages.request();
+1 -1
View File
@@ -10,7 +10,7 @@ class Settings extends DataModel<Settings> {
@override
String get id => '_';
final BelongsTo<User> user = BelongsTo();
bool isLoggedIn = false;
final HasMany<User> trustedUsers = HasMany();
}
mixin SettingsAdapter on Adapter<Settings> {
+15 -5
View File
@@ -15,6 +15,12 @@ mixin _$SettingsAdapter on Adapter<Settings> {
type: 'users',
kind: 'BelongsTo',
instance: (_) => (_ as Settings).user,
),
'trustedUsers': RelationshipMeta<User>(
name: 'trustedUsers',
type: 'users',
kind: 'HasMany',
instance: (_) => (_ as Settings).trustedUsers,
)
};
@@ -54,15 +60,19 @@ extension SettingsRelationshipGraphNodeX on RelationshipGraphNode<Settings> {
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
RelationshipGraphNode<User> get trustedUsers {
final meta = _$SettingsAdapter._kSettingsRelationshipMetas['trustedUsers']
as RelationshipMeta<User>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Settings _$SettingsFromJson(Map<String, dynamic> json) =>
Settings()..isLoggedIn = json['isLoggedIn'] as bool;
Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings();
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
'isLoggedIn': instance.isLoggedIn,
};
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{};
+1 -1
View File
@@ -76,7 +76,7 @@ class AppDetailScreen extends HookConsumerWidget {
child: Padding(
padding: const EdgeInsets.all(10),
child: Text(
'This update has a different certificate than the app installed on your device.\n\nHave you used another app store to install this app? If so, you can choose to remove it and re-install it using Zapstore.\n\nIt otherwise could be a malicious update, contact the developer for details.\n\nUpdate certificate hash: ${app.latestMetadata!.apkSignatureHash}',
'${app.name} ${app.latestMetadata!.version} has a different Android certificate than version ${app.localApp.value!.installedVersion} installed on your device.\n\nHave you used another app store to install ${app.name}? If so, you can choose to remove it and re-install coming back to this screen.\n\nIt otherwise could be a malicious update, contact the developer for details.\n\nNew certificate hash: ${app.latestMetadata!.apkSignatureHash}',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
+1 -1
View File
@@ -126,7 +126,7 @@ extension StringWidget on String {
}
String substringMax(int size) {
return substring(0, min(length, 80));
return substring(0, min(length, size));
}
}
+1 -1
View File
@@ -88,7 +88,7 @@ class AppCard extends HookConsumerWidget {
{kZapstoreAppIdentifier}.contains(app.identifier)))
AuthorContainer(
user: app.signer.value!,
text: 'Signed by',
beforeText: 'Signed by',
oneLine: true,
size: 12,
),
+13 -12
View File
@@ -1,18 +1,21 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:zapstore/models/user.dart';
import 'package:zapstore/utils/extensions.dart';
import 'package:zapstore/widgets/rounded_image.dart';
class AuthorContainer extends StatelessWidget {
final User user;
final String text;
final String beforeText;
final String afterText;
final bool oneLine;
final double size;
const AuthorContainer({
super.key,
required this.user,
this.text = 'Signed by',
this.beforeText = 'Signed by',
this.afterText = '',
this.oneLine = true,
this.size = 14,
});
@@ -28,7 +31,7 @@ class AuthorContainer extends StatelessWidget {
if (oneLine)
Expanded(
child: Text(
'$text ${user.nameOrNpub}',
'$beforeText ${user.nameOrNpub}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: size),
@@ -39,16 +42,14 @@ class AuthorContainer extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(text),
Padding(
padding: const EdgeInsets.only(left: 1),
child: Text(
user.nameOrNpub,
style: TextStyle(fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(beforeText),
Text(
user.nameOrNpub.substringMax(24),
style: TextStyle(fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(afterText),
],
)
],
+29 -28
View File
@@ -1,4 +1,5 @@
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:zapstore/main.data.dart';
@@ -9,7 +10,7 @@ import 'package:zapstore/widgets/app_drawer.dart';
import 'package:zapstore/widgets/author_container.dart';
import 'package:zapstore/widgets/wot_container.dart';
class InstallAlertDialog extends ConsumerWidget {
class InstallAlertDialog extends HookConsumerWidget {
const InstallAlertDialog({
super.key,
required this.app,
@@ -19,6 +20,7 @@ class InstallAlertDialog extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final trustedSignerNotifier = useState(false);
final user = ref.settings
.watchOne('_', alsoWatch: (_) => {_.user})
.model!
@@ -41,7 +43,9 @@ class InstallAlertDialog extends ConsumerWidget {
Gap(20),
if (app.signer.value != null)
AuthorContainer(
user: app.signer.value!, text: 'Signed by', oneLine: true),
user: app.signer.value!,
beforeText: 'Signed by',
oneLine: true),
Gap(20),
if (app.signer.value != null)
WebOfTrustContainer(
@@ -53,26 +57,24 @@ class InstallAlertDialog extends ConsumerWidget {
minimal: true,
labelText: 'Log in to view your own web of trust',
),
Gap(20),
if (app.latestMetadata?.urls.isNotEmpty ?? false)
RichText(
text: WidgetSpan(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text('This app will be downloaded from '),
Text(
Uri.parse(app.latestMetadata!.urls.first).host,
style: TextStyle(
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
),
),
Text(' and verified.'),
],
),
Gap(16),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Switch(
value: trustedSignerNotifier.value,
onChanged: (value) {
trustedSignerNotifier.value = value;
},
),
),
Gap(4),
Expanded(
child: Text(
'Do not ask again for ${app.signer.value!.name} apps',
),
)
],
)
],
),
),
@@ -81,16 +83,15 @@ class InstallAlertDialog extends ConsumerWidget {
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextButton(
onPressed: () {
app.install();
app.install(alwaysTrustSigner: trustedSignerNotifier.value);
// NOTE: can't use context.pop()
Navigator.of(context).pop();
},
child: user != null
? Text('Install', style: TextStyle(fontWeight: FontWeight.bold))
: Text(
'I trust the signer, install the app',
textAlign: TextAlign.right,
),
child: Text(
'${trustedSignerNotifier.value ? 'Always trust' : 'Trust'} ${app.signer.value!.name} and install app',
style: TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.right,
),
),
),
TextButton(
+10 -6
View File
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/models/local_app.dart';
import 'package:zapstore/utils/extensions.dart';
@@ -46,14 +47,17 @@ class InstallButton extends HookConsumerWidget {
_ => switch (progress) {
IdleInstallProgress() => () {
if (app.canInstall) {
showDialog(
context: context,
builder: (context) => InstallAlertDialog(app: app),
);
final settings = ref.settings.findOneLocalById('_')!;
if (settings.trustedUsers.contains(app.signer.value!)) {
app.install();
} else {
showDialog(
context: context,
builder: (context) => InstallAlertDialog(app: app),
);
}
} else if (app.canUpdate) {
app.install();
} else if (app.hasCertificateMismatch) {
// nothing
} else {
context.showError(
title: 'Installation not possible',
+21 -5
View File
@@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/widgets/author_container.dart';
class SignerAndDeveloperRow extends StatelessWidget {
class SignerAndDeveloperRow extends ConsumerWidget {
const SignerAndDeveloperRow({
super.key,
required this.app,
@@ -12,7 +14,9 @@ class SignerAndDeveloperRow extends StatelessWidget {
final App app;
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final settingsState = ref.settings.watchOne('_', remote: false);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -25,7 +29,7 @@ class SignerAndDeveloperRow extends StatelessWidget {
},
child: AuthorContainer(
user: app.developer.value!,
text: 'Developed by',
beforeText: 'Developed by',
oneLine: false),
),
if (app.signer.isPresent)
@@ -35,8 +39,20 @@ class SignerAndDeveloperRow extends StatelessWidget {
Uri.parse('https://njump.me/${app.signer.value!.npub}');
launchUrl(url);
},
child: AuthorContainer(
user: app.signer.value!, text: 'Signed by', oneLine: false),
child: Row(
children: [
AuthorContainer(
user: app.signer.value!,
beforeText: 'Signed by',
oneLine: false,
afterText: settingsState.hasModel &&
settingsState.model!.trustedUsers
.contains(app.signer.value!)
? '(trusted)'
: '',
),
],
),
),
],
);
@@ -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
+97 -60
View File
@@ -5,18 +5,23 @@ 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_manager:
dependency: "direct main"
description:
@@ -165,18 +170,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:
@@ -245,10 +250,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:
@@ -293,10 +298,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:
@@ -325,10 +330,10 @@ packages:
dependency: transitive
description:
name: equatable
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
url: "https://pub.dev"
source: hosted
version: "2.0.5"
version: "2.0.7"
fake_async:
dependency: transitive
description:
@@ -411,10 +416,10 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: f0e599ba89c9946c8e051780f0ec99aba4ba15895e0380a7ab68f420046fc44e
sha256: "255b00afa1a7bad19727da6a7780cf3db6c3c12e68d302d85e0ff1fdf173db9e"
url: "https://pub.dev"
source: hosted
version: "0.7.4+1"
version: "0.7.4+3"
flutter_phoenix:
dependency: "direct main"
description:
@@ -485,10 +490,10 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: ce89c5a993ca5eea74535f798478502c30a625ecb10a1de4d7fef5cd1bcac2a4
sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539"
url: "https://pub.dev"
source: hosted
version: "14.4.1"
version: "14.6.2"
graphs:
dependency: transitive
description:
@@ -621,10 +626,10 @@ packages:
dependency: "direct dev"
description:
name: json_serializable
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c
url: "https://pub.dev"
source: hosted
version: "6.8.0"
version: "6.9.0"
leak_tracker:
dependency: transitive
description:
@@ -665,6 +670,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
@@ -701,10 +714,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:
@@ -741,18 +754,18 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
sha256: "8c4967f8b7cb46dc914e178daa29813d83ae502e0529d7b0478330616a691ef7"
url: "https://pub.dev"
source: hosted
version: "2.2.10"
version: "2.2.14"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.1"
path_provider_linux:
dependency: transitive
description:
@@ -789,10 +802,10 @@ packages:
dependency: "direct main"
description:
name: percent_indicator
sha256: c37099ad833a883c9d71782321cb65c3a848c21b6939b6185f0ff6640d05814c
sha256: "0d77d5c6fa9b7f60202cedf748b568ba9ba38d3f30405d6ceae4da76f5185462"
url: "https://pub.dev"
source: hosted
version: "4.2.3"
version: "4.2.4"
permission_handler:
dependency: "direct main"
description:
@@ -821,10 +834,10 @@ packages:
dependency: transitive
description:
name: permission_handler_html
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+2"
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
@@ -950,18 +963,18 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.3.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
sha256: "7f172d1b06de5da47b6264c2692ee2ead20bbbc246690427cdb4fc301cd0c549"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.3.4"
shared_preferences_foundation:
dependency: transitive
description:
@@ -1014,10 +1027,10 @@ packages:
dependency: transitive
description:
name: shelf_web_socket
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
url: "https://pub.dev"
source: hosted
version: "2.0.0"
version: "2.0.1"
skeletonizer:
dependency: "direct main"
description:
@@ -1067,34 +1080,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: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
version: "2.5.4+6"
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: bb174b3ec2527f9c5f680f73a89af8149dd99782fbb56ea88ad0807c5638f2ed
sha256: cb7f4e9dc1b52b1fa350f7b3d41c662e75fc3d399555fa4e5efcf267e9a4fbb5
url: "https://pub.dev"
source: hosted
version: "2.4.7"
version: "2.5.0"
sqlite3_flutter_libs:
dependency: "direct main"
description:
name: sqlite3_flutter_libs
sha256: "7ae52b23366e5295005022e62fa093f64bfe190810223ea0ebf733a4cd140bce"
sha256: "636b0fe8a2de894e5455572f6cbbc458f4ffecfe9f860b79439e27041ea4f0b9"
url: "https://pub.dev"
source: hosted
version: "0.5.26"
version: "0.5.27"
stack_trace:
dependency: transitive
description:
@@ -1139,10 +1176,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:
@@ -1187,10 +1224,10 @@ 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"
universal_io:
dependency: transitive
description:
@@ -1211,34 +1248,34 @@ packages:
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:
name: url_launcher_ios
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
version: "6.3.2"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
@@ -1331,10 +1368,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a"
sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69"
url: "https://pub.dev"
source: hosted
version: "5.5.4"
version: "5.9.0"
win32_registry:
dependency: transitive
description:
@@ -1368,5 +1405,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"