From 1a2aa5edb96e09b1d14ca94ccb8faccb303e9aad Mon Sep 17 00:00:00 2001 From: otsmr Date: Mon, 15 Dec 2025 20:20:30 +0100 Subject: [PATCH] finishes #332 --- ios/Runner/Runner.entitlements | 4 ++ lib/src/localization/app_de.arb | 9 ++- lib/src/localization/app_en.arb | 9 ++- .../generated/app_localizations.dart | 34 +++++++++- .../generated/app_localizations_de.dart | 25 +++++++- .../generated/app_localizations_en.dart | 25 +++++++- lib/src/views/chats/add_new_user.view.dart | 23 ++++++- lib/src/views/home.view.dart | 63 +++++++++++++++++-- 8 files changed, 177 insertions(+), 15 deletions(-) diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index d2149cc..1e7475d 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -4,6 +4,10 @@ aps-environment development + com.apple.developer.associated-domains + + applinks:me.twonly.eu + keychain-access-groups $(AppIdentifierPrefix)eu.twonly.shared diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 2a9e71c..013022c 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -53,7 +53,7 @@ "searchUserNameBlockUserTooltip": "Benutzer ohne Benachrichtigung blockieren.", "searchUserNameRejectUserTooltip": "Die Anfrage ablehnen und den Anfragenden informieren.", "searchUserNameArchiveUserTooltip": "Benutzer archivieren. Du wirst informiert sobald er deine Anfrage akzeptiert.", - "userFound": "Benutzer gefunden", + "userFound": "{username} gefunden", "userFoundBody": "Möchtest du eine Folgeanfrage stellen?", "chatListViewSearchUserNameBtn": "Füge deinen ersten twonly-Kontakt hinzu!", "chatListViewSendFirstTwonly": "Sende dein erstes twonly!", @@ -446,5 +446,10 @@ "voiceMessageCancel": "Abbrechen", "shareYourProfile": "Teile dein Profil", "scanOtherProfile": "Scanne ein anderes Profil", - "skipForNow": "Vorerst überspringen" + "skipForNow": "Vorerst überspringen", + "linkFromUsername": "Ist der Link von {username}?", + "linkFromUsernameLong": "Wenn du den Link von der Person direkt erhalten hast, kannst du den Kontakt als verifiziert markieren, da der öffentliche Schlüssel im Link mit dem bereits für diesen Benutzer gespeicherten öffentlichen Schlüssel übereinstimmt.", + "gotLinkFromFriend": "Ja, der Link kommt direkt von der Person.", + "couldNotVerifyUsername": "{username} konnte nicht verifiziert werden", + "linkPubkeyDoesNotMatch": "Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!" } \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index a477d16..ac7128d 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -205,7 +205,7 @@ "addEmoji": "Emoji", "toggleFlashLight": "Toggle the flash light", "toggleHighQuality": "Toggle better resolution", - "userFound": "User found", + "userFound": "{username} found", "userFoundBody": "Do you want to create a follow request?", "searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.", "@searchUsernameNotFoundLong": { @@ -476,5 +476,10 @@ "voiceMessageCancel": "Cancel", "shareYourProfile": "Share your profile", "scanOtherProfile": "Scan other profile", - "skipForNow": "Skip for now" + "skipForNow": "Skip for now", + "linkFromUsername": "Is the link from {username}?", + "linkFromUsernameLong": "If you received the link from your friend, you can mark the user as verified, as the public key in the link matches the public key already stored for that user?", + "gotLinkFromFriend": "Yes, I got the link from my friend!", + "couldNotVerifyUsername": "Could not verify {username}", + "linkPubkeyDoesNotMatch": "The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!" } \ No newline at end of file diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 623c0d4..9aaf1e0 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -1181,8 +1181,8 @@ abstract class AppLocalizations { /// No description provided for @userFound. /// /// In en, this message translates to: - /// **'User found'** - String get userFound; + /// **'{username} found'** + String userFound(Object username); /// No description provided for @userFoundBody. /// @@ -2779,6 +2779,36 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Skip for now'** String get skipForNow; + + /// No description provided for @linkFromUsername. + /// + /// In en, this message translates to: + /// **'Is the link from {username}?'** + String linkFromUsername(Object username); + + /// No description provided for @linkFromUsernameLong. + /// + /// In en, this message translates to: + /// **'If you received the link from your friend, you can mark the user as verified, as the public key in the link matches the public key already stored for that user?'** + String get linkFromUsernameLong; + + /// No description provided for @gotLinkFromFriend. + /// + /// In en, this message translates to: + /// **'Yes, I got the link from my friend!'** + String get gotLinkFromFriend; + + /// No description provided for @couldNotVerifyUsername. + /// + /// In en, this message translates to: + /// **'Could not verify {username}'** + String couldNotVerifyUsername(Object username); + + /// No description provided for @linkPubkeyDoesNotMatch. + /// + /// In en, this message translates to: + /// **'The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!'** + String get linkPubkeyDoesNotMatch; } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 760fe24..075b646 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -598,7 +598,9 @@ class AppLocalizationsDe extends AppLocalizations { String get toggleHighQuality => 'Bessere Auflösung umschalten'; @override - String get userFound => 'Benutzer gefunden'; + String userFound(Object username) { + return '$username gefunden'; + } @override String get userFoundBody => 'Möchtest du eine Folgeanfrage stellen?'; @@ -1531,4 +1533,25 @@ class AppLocalizationsDe extends AppLocalizations { @override String get skipForNow => 'Vorerst überspringen'; + + @override + String linkFromUsername(Object username) { + return 'Ist der Link von $username?'; + } + + @override + String get linkFromUsernameLong => + 'Wenn du den Link von der Person direkt erhalten hast, kannst du den Kontakt als verifiziert markieren, da der öffentliche Schlüssel im Link mit dem bereits für diesen Benutzer gespeicherten öffentlichen Schlüssel übereinstimmt.'; + + @override + String get gotLinkFromFriend => 'Ja, der Link kommt direkt von der Person.'; + + @override + String couldNotVerifyUsername(Object username) { + return '$username konnte nicht verifiziert werden'; + } + + @override + String get linkPubkeyDoesNotMatch => + 'Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index ed0f8c0..d465133 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -593,7 +593,9 @@ class AppLocalizationsEn extends AppLocalizations { String get toggleHighQuality => 'Toggle better resolution'; @override - String get userFound => 'User found'; + String userFound(Object username) { + return '$username found'; + } @override String get userFoundBody => 'Do you want to create a follow request?'; @@ -1521,4 +1523,25 @@ class AppLocalizationsEn extends AppLocalizations { @override String get skipForNow => 'Skip for now'; + + @override + String linkFromUsername(Object username) { + return 'Is the link from $username?'; + } + + @override + String get linkFromUsernameLong => + 'If you received the link from your friend, you can mark the user as verified, as the public key in the link matches the public key already stored for that user?'; + + @override + String get gotLinkFromFriend => 'Yes, I got the link from my friend!'; + + @override + String couldNotVerifyUsername(Object username) { + return 'Could not verify $username'; + } + + @override + String get linkPubkeyDoesNotMatch => + 'The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!'; } diff --git a/lib/src/views/chats/add_new_user.view.dart b/lib/src/views/chats/add_new_user.view.dart index da17408..f127604 100644 --- a/lib/src/views/chats/add_new_user.view.dart +++ b/lib/src/views/chats/add_new_user.view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:drift/drift.dart' hide Column; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -16,7 +17,14 @@ import 'package:twonly/src/views/components/avatar_icon.component.dart'; import 'package:twonly/src/views/components/headline.dart'; class AddNewUserView extends StatefulWidget { - const AddNewUserView({super.key}); + const AddNewUserView({ + this.username, + this.publicKey, + super.key, + }); + + final String? username; + final Uint8List? publicKey; @override State createState() => _SearchUsernameView(); @@ -38,6 +46,13 @@ class _SearchUsernameView extends State { contacts = update; }), ); + + if (widget.username != null) { + searchUserName.text = widget.username!; + WidgetsBinding.instance.addPostFrameCallback((_) { + _addNewUser(context); + }); + } } @override @@ -73,7 +88,7 @@ class _SearchUsernameView extends State { final addUser = await showAlertDialog( context, - context.lang.userFound, + context.lang.userFound(searchUserName.text), context.lang.userFoundBody, ); @@ -88,6 +103,10 @@ class _SearchUsernameView extends State { requested: const Value(false), blocked: const Value(false), deletedByUser: const Value(false), + verified: Value( + !(widget.publicKey == null) && + userdata.publicIdentityKey.equals(widget.publicKey!), + ), ), ); diff --git a/lib/src/views/home.view.dart b/lib/src/views/home.view.dart index 0dab63d..282dfae 100644 --- a/lib/src/views/home.view.dart +++ b/lib/src/views/home.view.dart @@ -1,11 +1,14 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:typed_data'; import 'package:app_links/app_links.dart'; import 'package:collection/collection.dart'; +import 'package:drift/drift.dart' show Value; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:twonly/globals.dart'; +import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/notifications/setup.notifications.dart'; import 'package:twonly/src/services/signal/session.signal.dart'; @@ -15,7 +18,10 @@ import 'package:twonly/src/views/camera/camera_preview_components/camera_preview import 'package:twonly/src/views/camera/camera_preview_components/camera_preview_controller_view.dart'; import 'package:twonly/src/views/camera/camera_preview_components/main_camera_controller.dart'; import 'package:twonly/src/views/camera/share_image_editor_view.dart'; +import 'package:twonly/src/views/chats/add_new_user.view.dart'; import 'package:twonly/src/views/chats/chat_list.view.dart'; +import 'package:twonly/src/views/components/alert_dialog.dart'; +import 'package:twonly/src/views/contact/contact.view.dart'; import 'package:twonly/src/views/memories/memories.view.dart'; import 'package:twonly/src/views/public_profile.view.dart'; @@ -144,18 +150,65 @@ class HomeViewState extends State { final contacts = await twonlyDB.contactsDao.getContactsByUsername(username); if (contacts.isEmpty) { - // load user from server... + if (!mounted) return; + Uint8List? publicKeyBytes; + if (publicKey != null) { + publicKeyBytes = base64Url.decode(publicKey); + } + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return AddNewUserView( + username: username, + publicKey: publicKeyBytes, + ); + }, + ), + ); } else if (publicKey != null) { try { final contact = contacts.first; final storedPublicKey = await getPublicKeyFromContact(contact.userId); final receivedPublicKey = base64Url.decode(publicKey); - if (storedPublicKey == null || receivedPublicKey.isEmpty) return; - + if (storedPublicKey == null || + receivedPublicKey.isEmpty || + !mounted) { + return; + } if (storedPublicKey.equals(receivedPublicKey)) { - Log.info('Could verify the user'); + if (!contact.verified) { + final markAsVerified = await showAlertDialog( + context, + context.lang.linkFromUsername(contact.username), + context.lang.linkFromUsernameLong, + customOk: context.lang.gotLinkFromFriend, + ); + if (markAsVerified) { + await twonlyDB.contactsDao.updateContact( + contact.userId, + const ContactsCompanion( + verified: Value(true), + ), + ); + } + } else { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return ContactView(contact.userId); + }, + ), + ); + } } else { - Log.error('Show error message'); + await showAlertDialog( + context, + context.lang.couldNotVerifyUsername(contact.username), + context.lang.linkPubkeyDoesNotMatch, + customCancel: '', + ); } } catch (e) { Log.warn(e);