From f2b95386c63e82fe87853aff09249b5b40661a8e Mon Sep 17 00:00:00 2001 From: otsmr Date: Sat, 21 Feb 2026 01:01:25 +0100 Subject: [PATCH] feature: verification check mark --- assets/icons/verified_badge_green.svg | 4 ++ assets/icons/verified_badge_red.svg | 4 ++ assets/icons/verified_badge_yellow.svg | 4 ++ lib/src/constants/routes.keys.dart | 2 + .../generated/app_localizations.dart | 44 ++++++++++++- .../generated/app_localizations_de.dart | 28 ++++++++- .../generated/app_localizations_en.dart | 28 ++++++++- .../generated/app_localizations_sv.dart | 28 ++++++++- lib/src/localization/translations | 2 +- lib/src/providers/routing.provider.dart | 12 ++++ .../views/components/better_list_title.dart | 25 ++++---- lib/src/views/components/svg_icon.dart | 31 ++++++++++ lib/src/views/components/verified_shield.dart | 16 ++--- lib/src/views/contact/contact.view.dart | 23 ++++++- lib/src/views/public_profile.view.dart | 24 ++++++- .../views/settings/help/faq/verifybadge.dart | 62 +++++++++++++++++++ pubspec.yaml | 1 + 17 files changed, 308 insertions(+), 30 deletions(-) create mode 100644 assets/icons/verified_badge_green.svg create mode 100644 assets/icons/verified_badge_red.svg create mode 100644 assets/icons/verified_badge_yellow.svg create mode 100644 lib/src/views/components/svg_icon.dart create mode 100644 lib/src/views/settings/help/faq/verifybadge.dart diff --git a/assets/icons/verified_badge_green.svg b/assets/icons/verified_badge_green.svg new file mode 100644 index 0000000..98c69ac --- /dev/null +++ b/assets/icons/verified_badge_green.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/icons/verified_badge_red.svg b/assets/icons/verified_badge_red.svg new file mode 100644 index 0000000..6e70a82 --- /dev/null +++ b/assets/icons/verified_badge_red.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/icons/verified_badge_yellow.svg b/assets/icons/verified_badge_yellow.svg new file mode 100644 index 0000000..29ee80a --- /dev/null +++ b/assets/icons/verified_badge_yellow.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lib/src/constants/routes.keys.dart b/lib/src/constants/routes.keys.dart index aa8bab5..29ed8af 100644 --- a/lib/src/constants/routes.keys.dart +++ b/lib/src/constants/routes.keys.dart @@ -39,6 +39,8 @@ class Routes { static const String settingsStorageExport = '/settings/storage_data/export'; static const String settingsHelp = '/settings/help'; static const String settingsHelpFaq = '/settings/help/faq'; + static const String settingsHelpFaqVerifyBadge = + '/settings/help/faq/verifybadge'; static const String settingsHelpContactUs = '/settings/help/contact_us'; static const String settingsHelpDiagnostics = '/settings/help/diagnostics'; static const String settingsHelpUserStudy = '/settings/help/user_study'; diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 1821ee6..934c86e 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -943,7 +943,7 @@ abstract class AppLocalizations { /// No description provided for @contactVerifyNumberTitle. /// /// In en, this message translates to: - /// **'Verify safety number'** + /// **'Verify contact'** String get contactVerifyNumberTitle; /// No description provided for @contactVerifyNumberTapToScan. @@ -970,6 +970,12 @@ abstract class AppLocalizations { /// **'To verify the end-to-end encryption with {username}, compare the numbers with their device. The person can also scan your code with their device.'** String contactVerifyNumberLongDesc(Object username); + /// No description provided for @contactViewMessage. + /// + /// In en, this message translates to: + /// **'Message'** + String get contactViewMessage; + /// No description provided for @contactNickname. /// /// In en, this message translates to: @@ -2991,6 +2997,42 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'To see this message, you need to update twonly.'** String get updateTwonlyMessage; + + /// No description provided for @verificationBadgeNote. + /// + /// In en, this message translates to: + /// **'You can verify your friends by scanning their public QR code. Click to learn more.'** + String get verificationBadgeNote; + + /// No description provided for @verificationBadgeTitle. + /// + /// In en, this message translates to: + /// **'Verification'** + String get verificationBadgeTitle; + + /// No description provided for @verificationBadgeGeneralDesc. + /// + /// In en, this message translates to: + /// **'The green checkmark gives you the certainty that you are messaging the right person.'** + String get verificationBadgeGeneralDesc; + + /// No description provided for @verificationBadgeGreenDesc. + /// + /// In en, this message translates to: + /// **'Contact that you have personally verified via QR code. This also verified their public key.'** + String get verificationBadgeGreenDesc; + + /// No description provided for @verificationBadgeYellowDesc. + /// + /// In en, this message translates to: + /// **'(Coming soon) Contact whose QR code was scanned by one of your personally verified contacts.'** + String get verificationBadgeYellowDesc; + + /// No description provided for @verificationBadgeRedDesc. + /// + /// In en, this message translates to: + /// **'Unknown contact whose identity has not yet been verified.'** + String get verificationBadgeRedDesc; } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 4385fee..1ad8949 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -468,7 +468,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Dein Konto wird gelöscht. Es gibt keine Möglichkeit, es wiederherzustellen.'; @override - String get contactVerifyNumberTitle => 'Sicherheitsnummer verifizieren'; + String get contactVerifyNumberTitle => 'Benutzer verifizieren'; @override String get contactVerifyNumberTapToScan => 'Zum Scannen tippen'; @@ -484,6 +484,9 @@ class AppLocalizationsDe extends AppLocalizations { return '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.'; } + @override + String get contactViewMessage => 'Nachricht'; + @override String get contactNickname => 'Spitzname'; @@ -1669,4 +1672,27 @@ class AppLocalizationsDe extends AppLocalizations { @override String get updateTwonlyMessage => 'Um diese Nachricht zu sehen, musst du twonly aktualisieren.'; + + @override + String get verificationBadgeNote => + 'Du kannst deine Freunde verifizieren, indem du deren öffentlichen QR-Code scannst. Klicke, um mehr zu erfahren.'; + + @override + String get verificationBadgeTitle => 'Verifizierung'; + + @override + String get verificationBadgeGeneralDesc => + 'Der grüne Haken gibt dir die Sicherheit, dass du mit der richtigen Person schreibst.'; + + @override + String get verificationBadgeGreenDesc => + 'Kontakt, den du durch den QR-Code persönlich verifiziert hast. Dadurch wurde auch sein öffentlicher Schlüssel überprüft.'; + + @override + String get verificationBadgeYellowDesc => + '(Coming soon) Kontakt, dessen QR-Code von einem deiner persönlich verifizierten Kontakte gescannt wurde.'; + + @override + String get verificationBadgeRedDesc => + 'Unbekannter Kontakt, dessen Identität bisher nicht verifiziert wurde.'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 62fee3d..3513748 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -463,7 +463,7 @@ class AppLocalizationsEn extends AppLocalizations { 'Your account will be deleted. There is no change to restore it.'; @override - String get contactVerifyNumberTitle => 'Verify safety number'; + String get contactVerifyNumberTitle => 'Verify contact'; @override String get contactVerifyNumberTapToScan => 'Tap to scan'; @@ -479,6 +479,9 @@ class AppLocalizationsEn extends AppLocalizations { return 'To verify the end-to-end encryption with $username, compare the numbers with their device. The person can also scan your code with their device.'; } + @override + String get contactViewMessage => 'Message'; + @override String get contactNickname => 'Nickname'; @@ -1657,4 +1660,27 @@ class AppLocalizationsEn extends AppLocalizations { @override String get updateTwonlyMessage => 'To see this message, you need to update twonly.'; + + @override + String get verificationBadgeNote => + 'You can verify your friends by scanning their public QR code. Click to learn more.'; + + @override + String get verificationBadgeTitle => 'Verification'; + + @override + String get verificationBadgeGeneralDesc => + 'The green checkmark gives you the certainty that you are messaging the right person.'; + + @override + String get verificationBadgeGreenDesc => + 'Contact that you have personally verified via QR code. This also verified their public key.'; + + @override + String get verificationBadgeYellowDesc => + '(Coming soon) Contact whose QR code was scanned by one of your personally verified contacts.'; + + @override + String get verificationBadgeRedDesc => + 'Unknown contact whose identity has not yet been verified.'; } diff --git a/lib/src/localization/generated/app_localizations_sv.dart b/lib/src/localization/generated/app_localizations_sv.dart index 286fb3b..a32c895 100644 --- a/lib/src/localization/generated/app_localizations_sv.dart +++ b/lib/src/localization/generated/app_localizations_sv.dart @@ -463,7 +463,7 @@ class AppLocalizationsSv extends AppLocalizations { 'Your account will be deleted. There is no change to restore it.'; @override - String get contactVerifyNumberTitle => 'Verify safety number'; + String get contactVerifyNumberTitle => 'Verify contact'; @override String get contactVerifyNumberTapToScan => 'Tap to scan'; @@ -479,6 +479,9 @@ class AppLocalizationsSv extends AppLocalizations { return 'To verify the end-to-end encryption with $username, compare the numbers with their device. The person can also scan your code with their device.'; } + @override + String get contactViewMessage => 'Message'; + @override String get contactNickname => 'Nickname'; @@ -1657,4 +1660,27 @@ class AppLocalizationsSv extends AppLocalizations { @override String get updateTwonlyMessage => 'To see this message, you need to update twonly.'; + + @override + String get verificationBadgeNote => + 'You can verify your friends by scanning their public QR code. Click to learn more.'; + + @override + String get verificationBadgeTitle => 'Verification'; + + @override + String get verificationBadgeGeneralDesc => + 'The green checkmark gives you the certainty that you are messaging the right person.'; + + @override + String get verificationBadgeGreenDesc => + 'Contact that you have personally verified via QR code. This also verified their public key.'; + + @override + String get verificationBadgeYellowDesc => + '(Coming soon) Contact whose QR code was scanned by one of your personally verified contacts.'; + + @override + String get verificationBadgeRedDesc => + 'Unknown contact whose identity has not yet been verified.'; } diff --git a/lib/src/localization/translations b/lib/src/localization/translations index 69d295d..6147155 160000 --- a/lib/src/localization/translations +++ b/lib/src/localization/translations @@ -1 +1 @@ -Subproject commit 69d295db737253e0c1b68aedc39bf757e8d642e6 +Subproject commit 6147155ce50caa97864d56e42e49a6f54702785d diff --git a/lib/src/providers/routing.provider.dart b/lib/src/providers/routing.provider.dart index 395a7fb..87f7c4b 100644 --- a/lib/src/providers/routing.provider.dart +++ b/lib/src/providers/routing.provider.dart @@ -32,6 +32,7 @@ import 'package:twonly/src/views/settings/help/contact_us.view.dart'; import 'package:twonly/src/views/settings/help/credits.view.dart'; import 'package:twonly/src/views/settings/help/diagnostics.view.dart'; import 'package:twonly/src/views/settings/help/faq.view.dart'; +import 'package:twonly/src/views/settings/help/faq/verifybadge.dart'; import 'package:twonly/src/views/settings/help/help.view.dart'; import 'package:twonly/src/views/settings/notification.view.dart'; import 'package:twonly/src/views/settings/privacy.view.dart'; @@ -227,6 +228,12 @@ final routerProvider = GoRouter( GoRoute( path: 'faq', builder: (context, state) => const FaqView(), + routes: [ + GoRoute( + path: 'verifybadge', + builder: (context, state) => const VerificationBadeFaqView(), + ), + ], ), GoRoute( path: 'contact_us', @@ -281,5 +288,10 @@ final routerProvider = GoRouter( ), ], ), + // Fallback instead of showing a Page Not Found error redirect to home + GoRoute( + path: '/:path(.*)', + redirect: (context, state) => '/', + ), ], ); diff --git a/lib/src/views/components/better_list_title.dart b/lib/src/views/components/better_list_title.dart index fe408a5..fac2f5e 100644 --- a/lib/src/views/components/better_list_title.dart +++ b/lib/src/views/components/better_list_title.dart @@ -27,20 +27,17 @@ class BetterListTile extends StatelessWidget { @override Widget build(BuildContext context) { return ListTile( - leading: Padding( - padding: (padding == null) - ? const EdgeInsets.only( - right: 10, - left: 19, - ) - : padding!, - child: (leading != null) - ? leading - : FaIcon( - icon, - size: iconSize, - color: color, - ), + leading: SizedBox( + width: 50, + child: Center( + child: (leading != null) + ? leading + : FaIcon( + icon, + size: iconSize, + color: color, + ), + ), ), trailing: trailing, title: Text( diff --git a/lib/src/views/components/svg_icon.dart b/lib/src/views/components/svg_icon.dart new file mode 100644 index 0000000..efd067a --- /dev/null +++ b/lib/src/views/components/svg_icon.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class SvgIcons { + static const String verifiedGreen = 'assets/icons/verified_badge_green.svg'; + static const String verifiedYellow = 'assets/icons/verified_badge_yellow.svg'; + static const String verifiedRed = 'assets/icons/verified_badge_red.svg'; +} + +class SvgIcon extends StatelessWidget { + const SvgIcon({ + required this.assetPath, + super.key, + this.size = 24.0, + this.color, + }); + final String assetPath; + final double? size; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + assetPath, + width: size, + height: size, + colorFilter: + color != null ? ColorFilter.mode(color!, BlendMode.srcIn) : null, + ); + } +} diff --git a/lib/src/views/components/verified_shield.dart b/lib/src/views/components/verified_shield.dart index 73b6ab2..a18684d 100644 --- a/lib/src/views/components/verified_shield.dart +++ b/lib/src/views/components/verified_shield.dart @@ -1,17 +1,17 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:go_router/go_router.dart'; import 'package:twonly/globals.dart'; import 'package:twonly/src/constants/routes.keys.dart'; import 'package:twonly/src/database/twonly.db.dart'; +import 'package:twonly/src/views/components/svg_icon.dart'; class VerifiedShield extends StatefulWidget { const VerifiedShield({ this.contact, this.group, super.key, - this.size = 18, + this.size = 15, }); final Group? group; final Contact? contact; @@ -64,11 +64,13 @@ class _VerifiedShieldState extends State { message: isVerified ? 'You verified this contact' : 'You have not verifies this contact.', - child: FaIcon( - isVerified ? FontAwesomeIcons.shieldHeart : Icons.gpp_maybe_rounded, - color: - isVerified ? Theme.of(context).colorScheme.primary : Colors.red, - size: widget.size, + child: Padding( + padding: const EdgeInsetsGeometry.only(top: 2), + child: SvgIcon( + assetPath: + isVerified ? SvgIcons.verifiedGreen : SvgIcons.verifiedRed, + size: widget.size, + ), ), ), ); diff --git a/lib/src/views/contact/contact.view.dart b/lib/src/views/contact/contact.view.dart index 41bc54b..b0fc162 100644 --- a/lib/src/views/contact/contact.view.dart +++ b/lib/src/views/contact/contact.view.dart @@ -14,6 +14,7 @@ import 'package:twonly/src/views/components/better_list_title.dart'; import 'package:twonly/src/views/components/flame.dart'; import 'package:twonly/src/views/components/max_flame_list_title.dart'; import 'package:twonly/src/views/components/select_chat_deletion_time.comp.dart'; +import 'package:twonly/src/views/components/svg_icon.dart'; import 'package:twonly/src/views/components/verified_shield.dart'; import 'package:twonly/src/views/groups/group.view.dart'; @@ -163,6 +164,21 @@ class _ContactViewState extends State { if (getContactDisplayName(contact) != contact.username) Center(child: Text('(${contact.username})')), const SizedBox(height: 50), + BetterListTile( + icon: FontAwesomeIcons.solidComments, + text: context.lang.contactViewMessage, + onTap: () async { + final group = + await twonlyDB.groupsDao.getDirectChat(contact.userId); + if (group != null && context.mounted) { + await context.push( + Routes.chatsMessages, + extra: group, + ); + } + }, + ), + const Divider(), BetterListTile( icon: FontAwesomeIcons.pencil, text: context.lang.contactNickname, @@ -176,7 +192,6 @@ class _ContactViewState extends State { } }, ), - const Divider(), SelectChatDeletionTimeListTitle( groupId: getUUIDforDirectChat(widget.userId, gUser.userId), ), @@ -185,7 +200,11 @@ class _ContactViewState extends State { contactId: widget.userId, ), BetterListTile( - icon: FontAwesomeIcons.shieldHeart, + leading: SvgIcon( + assetPath: SvgIcons.verifiedGreen, + size: 20, + color: IconTheme.of(context).color, + ), text: context.lang.contactVerifyNumberTitle, onTap: () async { await context.push(Routes.settingsPublicProfile); diff --git a/lib/src/views/public_profile.view.dart b/lib/src/views/public_profile.view.dart index e3a937b..a2bb355 100644 --- a/lib/src/views/public_profile.view.dart +++ b/lib/src/views/public_profile.view.dart @@ -45,9 +45,29 @@ class _PublicProfileViewState extends State { body: Column( children: [ Container(width: double.infinity), - const SizedBox( - height: 30, + const SizedBox(height: 10), + GestureDetector( + onTap: () => context.push(Routes.settingsHelpFaqVerifyBadge), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 30), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + color: context.color.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Text(context.lang.verificationBadgeNote), + ), + const SizedBox(width: 10), + const FaIcon(FontAwesomeIcons.angleRight), + ], + ), + ), ), + const SizedBox(height: 20), if (_qrCode != null && _userAvatar != null) Container( decoration: BoxDecoration( diff --git a/lib/src/views/settings/help/faq/verifybadge.dart b/lib/src/views/settings/help/faq/verifybadge.dart new file mode 100644 index 0000000..f3efb3d --- /dev/null +++ b/lib/src/views/settings/help/faq/verifybadge.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:twonly/src/utils/misc.dart'; +import 'package:twonly/src/views/components/svg_icon.dart'; + +class VerificationBadeFaqView extends StatefulWidget { + const VerificationBadeFaqView({super.key}); + + @override + State createState() => + _VerificationBadeFaqViewState(); +} + +class _VerificationBadeFaqViewState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.lang.verificationBadgeTitle), + ), + body: ListView( + padding: const EdgeInsets.all(40), + children: [ + Text( + context.lang.verificationBadgeGeneralDesc, + textAlign: TextAlign.center, + ), + const SizedBox(height: 40), + _buildItem( + icon: const SvgIcon(assetPath: SvgIcons.verifiedGreen, size: 40), + description: context.lang.verificationBadgeGreenDesc, + ), + _buildItem( + icon: const SvgIcon(assetPath: SvgIcons.verifiedYellow, size: 40), + description: context.lang.verificationBadgeYellowDesc, + ), + _buildItem( + icon: const SvgIcon(assetPath: SvgIcons.verifiedRed, size: 40), + description: context.lang.verificationBadgeRedDesc, + ), + ], + ), + ); + } + + Widget _buildItem({required Widget icon, required String description}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 40), + child: Row( + children: [ + icon, + const SizedBox(width: 20), + Expanded( + child: Text( + description, + style: const TextStyle(fontSize: 16, height: 1.4), + ), + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 0b269c2..2419c31 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -203,6 +203,7 @@ flutter: assets: # Add assets from the images directory to the application. - assets/images/ + - assets/icons/ - assets/animated_icons/ - assets/animations/ - assets/passwords/