This commit is contained in:
otsmr 2025-05-31 13:42:12 +02:00
parent ebc4570b9b
commit 27fa177a2e
18 changed files with 266 additions and 106 deletions

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "dependencies/flutter_secure_storage"] [submodule "dependencies/flutter_secure_storage"]
path = dependencies/flutter_secure_storage path = dependencies/flutter_secure_storage
url = https://github.com/juliansteenbakker/flutter_secure_storage url = https://github.com/juliansteenbakker/flutter_secure_storage
[submodule "dependencies/flutter_zxing"]
path = dependencies/flutter_zxing
url = https://github.com/khoren93/flutter_zxing.git

View file

@ -1,5 +1,5 @@
{ {
"files.exclude": { "files.watcherExclude": {
"dependencies": false "dependencies": false
} }
} }

View file

@ -13,5 +13,9 @@ pub.dev or because they require some special installation.
```bash ```bash
git submodule update --init --recursive git submodule update --init --recursive
cd dependencies/flutter_zxing
git submodule update --init --recursive
./scripts/update_ios_macos_src.s
``` ```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dependencies/flutter_zxing vendored Submodule

@ -0,0 +1 @@
Subproject commit ba65f2fb4a09f4e68f6de64aa1de41ba3dc4977e

View file

@ -71,6 +71,8 @@ PODS:
- flutter_secure_storage_darwin (10.0.0): - flutter_secure_storage_darwin (10.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- flutter_zxing (0.0.1):
- Flutter
- gal (1.0.0): - gal (1.0.0):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@ -234,6 +236,7 @@ DEPENDENCIES:
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
- flutter_zxing (from `.symlinks/plugins/flutter_zxing/ios`)
- gal (from `.symlinks/plugins/gal/darwin`) - gal (from `.symlinks/plugins/gal/darwin`)
- GoogleUtilities - GoogleUtilities
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
@ -290,6 +293,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_local_notifications/ios" :path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_secure_storage_darwin: flutter_secure_storage_darwin:
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
flutter_zxing:
:path: ".symlinks/plugins/flutter_zxing/ios"
gal: gal:
:path: ".symlinks/plugins/gal/darwin" :path: ".symlinks/plugins/gal/darwin"
image_picker_ios: image_picker_ios:
@ -337,6 +342,7 @@ SPEC CHECKSUMS:
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619 flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468 flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468
flutter_zxing: e8bcc43bd3056c70c271b732ed94e7a16fd62f93
gal: baecd024ebfd13c441269ca7404792a7152fde89 gal: baecd024ebfd13c441269ca7404792a7152fde89
GoogleAppMeasurement: 36684bfb3ee034e2b42b4321eb19da3a1b81e65d GoogleAppMeasurement: 36684bfb3ee034e2b42b4321eb19da3a1b81e65d
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7

View file

@ -131,6 +131,7 @@
"settingsAccountDeleteModalTitle": "Bist du sicher?", "settingsAccountDeleteModalTitle": "Bist du sicher?",
"settingsAccountDeleteModalBody": "Dein Konto wird gelöscht. Es gibt keine Möglichkeit, es wiederherzustellen.", "settingsAccountDeleteModalBody": "Dein Konto wird gelöscht. Es gibt keine Möglichkeit, es wiederherzustellen.",
"contactVerifyNumberTitle": "Sicherheitsnummer verifizieren", "contactVerifyNumberTitle": "Sicherheitsnummer verifizieren",
"contactVerifyNumberTapToScan": "Zum Scannen tippen",
"contactVerifyNumberMarkAsVerified": "Als verifiziert markieren", "contactVerifyNumberMarkAsVerified": "Als verifiziert markieren",
"contactVerifyNumberClearVerification": "Verifizierung aufheben", "contactVerifyNumberClearVerification": "Verifizierung aufheben",
"contactVerifyNumberLongDesc": "Um die Ende-zu-Ende-Verschlüsselung mit {username} zu verifizieren, vergleiche die Zahlen mit ihrem Gerät. Die Person kann auch deinen Code mit ihrem Gerät scannen.", "contactVerifyNumberLongDesc": "Um die Ende-zu-Ende-Verschlüsselung mit {username} zu verifizieren, vergleiche die Zahlen mit ihrem Gerät. Die Person kann auch deinen Code mit ihrem Gerät scannen.",

View file

@ -231,6 +231,8 @@
"@settingsAccountDeleteModalBody": {}, "@settingsAccountDeleteModalBody": {},
"contactVerifyNumberTitle": "Verify safety number", "contactVerifyNumberTitle": "Verify safety number",
"@contactVerifyNumberTitle": {}, "@contactVerifyNumberTitle": {},
"contactVerifyNumberTapToScan": "Tap to scan",
"@contactVerifyNumberTapToScan": {},
"contactVerifyNumberMarkAsVerified": "Mark as verified", "contactVerifyNumberMarkAsVerified": "Mark as verified",
"@contactVerifyNumberMarkAsVerified": {}, "@contactVerifyNumberMarkAsVerified": {},
"contactVerifyNumberClearVerification": "Clear verification", "contactVerifyNumberClearVerification": "Clear verification",

View file

@ -788,6 +788,12 @@ abstract class AppLocalizations {
/// **'Verify safety number'** /// **'Verify safety number'**
String get contactVerifyNumberTitle; String get contactVerifyNumberTitle;
/// No description provided for @contactVerifyNumberTapToScan.
///
/// In en, this message translates to:
/// **'Tap to scan'**
String get contactVerifyNumberTapToScan;
/// No description provided for @contactVerifyNumberMarkAsVerified. /// No description provided for @contactVerifyNumberMarkAsVerified.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View file

@ -385,6 +385,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get contactVerifyNumberTitle => 'Sicherheitsnummer verifizieren'; String get contactVerifyNumberTitle => 'Sicherheitsnummer verifizieren';
@override
String get contactVerifyNumberTapToScan => 'Zum Scannen tippen';
@override @override
String get contactVerifyNumberMarkAsVerified => 'Als verifiziert markieren'; String get contactVerifyNumberMarkAsVerified => 'Als verifiziert markieren';

View file

@ -380,6 +380,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get contactVerifyNumberTitle => 'Verify safety number'; String get contactVerifyNumberTitle => 'Verify safety number';
@override
String get contactVerifyNumberTapToScan => 'Tap to scan';
@override @override
String get contactVerifyNumberMarkAsVerified => 'Mark as verified'; String get contactVerifyNumberMarkAsVerified => 'Mark as verified';

View file

@ -30,7 +30,7 @@ class FormattedStringWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SelectableText( return SelectableText(
formatString(longString), formatString(longString),
style: TextStyle(fontSize: 18, color: Colors.black), style: TextStyle(fontSize: 16, color: Colors.black),
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
} }

View file

@ -1,8 +1,10 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:drift/drift.dart' hide Column; import 'package:drift/drift.dart' hide Column;
import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart'; import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:lottie/lottie.dart';
import 'package:twonly/globals.dart'; import 'package:twonly/globals.dart';
import 'package:twonly/src/services/signal/session.signal.dart'; import 'package:twonly/src/services/signal/session.signal.dart';
import 'package:twonly/src/views/components/format_long_string.dart'; import 'package:twonly/src/views/components/format_long_string.dart';
@ -10,7 +12,9 @@ import 'package:flutter/material.dart';
import 'package:twonly/src/database/daos/contacts_dao.dart'; import 'package:twonly/src/database/daos/contacts_dao.dart';
import 'package:twonly/src/database/twonly_database.dart'; import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/contact/contact_verify_qr_scan.view.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:image/image.dart' as imglib;
class ContactVerifyView extends StatefulWidget { class ContactVerifyView extends StatefulWidget {
const ContactVerifyView(this.contact, {super.key}); const ContactVerifyView(this.contact, {super.key});
@ -20,31 +24,127 @@ class ContactVerifyView extends StatefulWidget {
State<ContactVerifyView> createState() => _ContactVerifyViewState(); State<ContactVerifyView> createState() => _ContactVerifyViewState();
} }
enum ScanResult { None, Success, Failed }
class _ContactVerifyViewState extends State<ContactVerifyView> { class _ContactVerifyViewState extends State<ContactVerifyView> {
Fingerprint? fingerprint; Fingerprint? _fingerprint;
late Contact _contact;
late StreamSubscription<Contact?> _contactSub;
ScanResult _scanResult = ScanResult.None;
Uint8List? _qrCodeImageBytes;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_contact = widget.contact;
loadAsync(); loadAsync();
} }
Future loadAsync() async { @override
fingerprint = await generateSessionFingerPrint(widget.contact.userId); void dispose() {
setState(() {}); _contactSub.cancel();
super.dispose();
}
Future loadAsync() async {
_fingerprint = await generateSessionFingerPrint(widget.contact.userId);
if (_fingerprint != null) {
final Encode result = zx.encodeBarcode(
contents: base64Encode(
_fingerprint!.scannableFingerprint.fingerprints,
),
params: EncodeParams(
format: Format.qrCode,
width: 150,
height: 150,
margin: 0,
eccLevel: EccLevel.low,
),
);
if (result.isValid && result.data != null) {
final img = imglib.Image.fromBytes(
width: 150,
height: 150,
bytes: result.data!.buffer,
numChannels: 1,
);
_qrCodeImageBytes = imglib.encodePng(img);
}
} }
@override
Widget build(BuildContext context) {
Stream<Contact?> contact = twonlyDB.contactsDao Stream<Contact?> contact = twonlyDB.contactsDao
.getContactByUserId(widget.contact.userId) .getContactByUserId(widget.contact.userId)
.watchSingleOrNull(); .watchSingleOrNull();
_contactSub = contact.listen((contact) {
if (contact == null) return;
setState(() {
_contact = contact;
});
});
setState(() {});
}
Future openQrScanner() async {
if (_fingerprint == null) return;
bool? isValid = await Navigator.push(context, MaterialPageRoute(
builder: (context) {
return ContactVerifyQrScanView(
widget.contact,
fingerprint: _fingerprint!,
);
},
));
if (isValid == null) {
return; // user just returned...
}
if (isValid) {
_scanResult = ScanResult.Success;
updateUserVerifyState(true);
} else {
_scanResult = ScanResult.Failed;
updateUserVerifyState(false);
}
setState(() {});
}
Future updateUserVerifyState(bool verified) async {
final update = ContactsCompanion(verified: Value(verified));
await twonlyDB.contactsDao.updateContact(_contact.userId, update);
}
Widget get qrWidget => (_qrCodeImageBytes == null)
? SizedBox(
width: 150,
height: 150,
)
: Image.memory(_qrCodeImageBytes!);
Widget get resultAnimation => SizedBox(
width: 150,
child: Lottie.asset(
(_scanResult == ScanResult.Success)
? 'assets/animations/success.json'
: 'assets/animations/failed.json',
repeat: false,
onLoaded: (p0) {
Future.delayed(Duration(seconds: 3), () {
if (mounted) {
setState(() {
_scanResult = ScanResult.None;
});
}
});
},
),
);
@override
Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(context.lang.contactVerifyNumberTitle), title: Text(context.lang.contactVerifyNumberTitle),
), ),
body: (fingerprint == null) body: (_fingerprint == null)
? Center(child: CircularProgressIndicator()) ? Center(child: CircularProgressIndicator())
: ListView( : ListView(
children: [ children: [
@ -57,6 +157,8 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
child: GestureDetector(
onTap: openQrScanner,
child: Column( child: Column(
children: [ children: [
Container( Container(
@ -64,26 +166,33 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
color: Colors.white, color: Colors.white,
), ),
child: QrImageView( padding: EdgeInsets.symmetric(vertical: 20),
data: base64Encode(fingerprint! child: Column(
.scannableFingerprint.fingerprints), children: [
version: QrVersions.auto, (_scanResult == ScanResult.None)
size: 150.0, ? qrWidget
), : resultAnimation,
),
SizedBox(height: 10), SizedBox(height: 10),
SizedBox( SizedBox(
width: 200, width: 200,
child: Text( child: Text(
"QR Code scanning is coming soon. Please compare the numbers manual.", (_scanResult == ScanResult.None)
style: ? context
TextStyle(color: Colors.black, fontSize: 10), .lang.contactVerifyNumberTapToScan
: "",
style: TextStyle(
color: Colors.black,
fontSize: 15,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
],
),
),
SizedBox(height: 20), SizedBox(height: 20),
FormattedStringWidget( FormattedStringWidget(
fingerprint!.displayableFingerprint _fingerprint!.displayableFingerprint
.getDisplayText(), .getDisplayText(),
), ),
], ],
@ -91,21 +200,14 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
), ),
), ),
), ),
StreamBuilder( ),
stream: contact, Padding(
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return Container();
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30), padding: const EdgeInsets.symmetric(horizontal: 30),
child: Text( child: Text(
context.lang.contactVerifyNumberLongDesc( context.lang.contactVerifyNumberLongDesc(
getContactDisplayName(snapshot.data!)), getContactDisplayName(_contact)),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
);
},
), ),
Padding( Padding(
padding: padding:
@ -133,36 +235,19 @@ class _ContactVerifyViewState extends State<ContactVerifyView> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
StreamBuilder( (_contact.verified)
stream: contact, ? OutlinedButton.icon(
builder: (context, snapshot) { onPressed: () => updateUserVerifyState(false),
if (!snapshot.hasData || snapshot.data == null) {
return Container();
}
final contact = snapshot.data!;
if (contact.verified) {
return OutlinedButton.icon(
onPressed: () {
final update =
ContactsCompanion(verified: Value(false));
twonlyDB.contactsDao
.updateContact(contact.userId, update);
},
label: Text( label: Text(
context.lang.contactVerifyNumberClearVerification), context.lang.contactVerifyNumberClearVerification),
); )
} : FilledButton.icon(
return FilledButton.icon(
icon: FaIcon(FontAwesomeIcons.shieldHeart), icon: FaIcon(FontAwesomeIcons.shieldHeart),
onPressed: () { onPressed: () => updateUserVerifyState(true),
final update = ContactsCompanion(verified: Value(true)); label: Text(
twonlyDB.contactsDao context.lang.contactVerifyNumberMarkAsVerified,
.updateContact(contact.userId, update);
},
label: Text(context.lang.contactVerifyNumberMarkAsVerified),
);
},
), ),
)
], ],
), ),
), ),

View file

@ -0,0 +1,43 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
import 'package:twonly/src/database/twonly_database.dart';
import 'package:twonly/src/utils/log.dart';
class ContactVerifyQrScanView extends StatefulWidget {
const ContactVerifyQrScanView(this.contact,
{super.key, required this.fingerprint});
final Fingerprint fingerprint;
final Contact contact;
@override
State<ContactVerifyQrScanView> createState() =>
_ContactVerifyQrScanViewState();
}
class _ContactVerifyQrScanViewState extends State<ContactVerifyQrScanView> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ReaderWidget(
onScan: (result) async {
bool isValid = false;
try {
if (result.text != null) {
Uint8List otherFingerPrint = base64Decode(result.text!);
isValid = widget.fingerprint.scannableFingerprint.compareTo(
otherFingerPrint,
);
}
} catch (e) {
Log.error("$e");
}
return Navigator.pop(context, isValid);
},
),
);
}
}

View file

@ -63,40 +63,51 @@ class CreditsView extends StatelessWidget {
)), )),
), ),
UrlListTitle( UrlListTitle(
title: "Free selfie fast Animation", title: "selfie fast Animation",
subtitle: "Brandon Ambuila", subtitle: "Brandon Ambuila",
url: url:
"https://lottiefiles.com/free-animation/selfie-fast-JZx4Ftrg1E", "https://lottiefiles.com/free-animation/selfie-fast-JZx4Ftrg1E",
), ),
UrlListTitle( UrlListTitle(
title: "Free Security status - Safe Animation", title: "Security status - Safe Animation",
subtitle: "Yogesh Pal", subtitle: "Yogesh Pal",
url: url:
"https://lottiefiles.com/free-animation/security-status-safe-CePJPAwLVx", "https://lottiefiles.com/free-animation/security-status-safe-CePJPAwLVx",
), ),
UrlListTitle( UrlListTitle(
title: "Free send mail Animation", title: "send mail Animation",
subtitle: "jignesh gajjar", subtitle: "jignesh gajjar",
url: "https://lottiefiles.com/free-animation/send-mail-3pvzm2kmNq", url: "https://lottiefiles.com/free-animation/send-mail-3pvzm2kmNq",
), ),
UrlListTitle( UrlListTitle(
title: "Free Present for you Animation", title: "Present for you Animation",
subtitle: "Tatsiana Melnikova", subtitle: "Tatsiana Melnikova",
url: url:
"https://lottiefiles.com/free-animation/present-for-you-QalWyuNptY", "https://lottiefiles.com/free-animation/present-for-you-QalWyuNptY",
), ),
UrlListTitle( UrlListTitle(
title: "Free Take a photo Animation", title: "Take a photo Animation",
subtitle: "Nguyễn Như Lân", subtitle: "Nguyễn Như Lân",
url: url:
"https://lottiefiles.com/free-animation/take-a-photo-CzOUerxwPP?color-palette=true", "https://lottiefiles.com/free-animation/take-a-photo-CzOUerxwPP?color-palette=true",
), ),
UrlListTitle( UrlListTitle(
title: "Kostenlose Valentine's Day-Animation", title: "Valentine's Day-Animation",
subtitle: "Strezha", subtitle: "Strezha",
url: url:
"https://lottiefiles.com/de/free-animation/valentines-day-1UiMkPHnPK?color-palette=true", "https://lottiefiles.com/de/free-animation/valentines-day-1UiMkPHnPK?color-palette=true",
), ),
UrlListTitle(
title: "success-Animation",
subtitle: "Aman Awasthy",
url:
"https://lottiefiles.com/de/free-animation/success-tick-cuwjLHAR7g",
),
UrlListTitle(
title: "Failed-Animation",
subtitle: "Ahmed Shami أحمد شامي",
url: "https://lottiefiles.com/de/free-animation/failed-e5cQFDEtLv",
),
const Divider(), const Divider(),
ListTile( ListTile(
title: Center( title: Center(
@ -105,18 +116,6 @@ class CreditsView extends StatelessWidget {
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
)), )),
), ),
UrlListTitle(
title: "Germany",
subtitle: "by GDJ",
url:
"https://pixabay.com/vectors/republic-germany-deutschland-map-1220652/",
),
UrlListTitle(
title: "Frankfurt am Main",
subtitle: "by GDJ",
url:
"https://pixabay.com/vectors/frankfurt-germany-skyline-cityscape-3166262/",
),
UrlListTitle( UrlListTitle(
title: "Avo Cardio", title: "Avo Cardio",
subtitle: "by RalfDesign", subtitle: "by RalfDesign",
@ -129,12 +128,6 @@ class CreditsView extends StatelessWidget {
url: url:
"https://pixabay.com/illustrations/sloth-swimming-summer-pool-cartoon-4575121/", "https://pixabay.com/illustrations/sloth-swimming-summer-pool-cartoon-4575121/",
), ),
UrlListTitle(
title: "Sloth",
subtitle: "by RalfDesign",
url:
"https://pixabay.com/illustrations/sloth-swimming-summer-pool-cartoon-4575121/",
),
UrlListTitle( UrlListTitle(
title: "Duck", title: "Duck",
subtitle: "by lachkegeetanjali", subtitle: "by lachkegeetanjali",

View file

@ -716,6 +716,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_zxing:
dependency: "direct main"
description:
path: "dependencies/flutter_zxing"
relative: true
source: path
version: "2.1.0"
font_awesome_flutter: font_awesome_flutter:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -25,6 +25,8 @@ dependencies:
# flutter_secure_storage: ^10.0.0-beta.4 # flutter_secure_storage: ^10.0.0-beta.4
flutter_secure_storage: flutter_secure_storage:
path: ./dependencies/flutter_secure_storage/flutter_secure_storage path: ./dependencies/flutter_secure_storage/flutter_secure_storage
flutter_zxing:
path: ./dependencies/flutter_zxing
font_awesome_flutter: ^10.8.0 font_awesome_flutter: ^10.8.0
gal: ^2.3.1 gal: ^2.3.1
hand_signature: ^3.0.3 hand_signature: ^3.0.3
@ -46,7 +48,6 @@ dependencies:
protobuf: ^4.0.0 protobuf: ^4.0.0
cryptography_plus: ^2.7.0 cryptography_plus: ^2.7.0
provider: ^6.1.2 provider: ^6.1.2
qr_flutter: ^4.1.0
restart_app: ^1.3.2 restart_app: ^1.3.2
screenshot: ^3.0.0 screenshot: ^3.0.0
url_launcher: ^6.3.1 url_launcher: ^6.3.1