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/