mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-07-18 01:14:07 +00:00
Improve: Visibility of the verification badge
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
cda52a5da8
commit
cda7df36f3
19 changed files with 588 additions and 147 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## 0.3.5
|
## 0.3.5
|
||||||
|
|
||||||
|
- Improve: Visibility of the verification badge
|
||||||
- Fix: Performance issue caused by an out-of-sync Signal session
|
- Fix: Performance issue caused by an out-of-sync Signal session
|
||||||
- Fix: Multiple smaller bug fixes
|
- Fix: Multiple smaller bug fixes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ import 'package:twonly/src/localization/generated/app_localizations.dart';
|
||||||
import 'package:twonly/src/model/json/onboarding_state.model.dart';
|
import 'package:twonly/src/model/json/onboarding_state.model.dart';
|
||||||
import 'package:twonly/src/providers/routing.provider.dart';
|
import 'package:twonly/src/providers/routing.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/services/passwordless_recovery.service.dart'
|
|
||||||
show PasswordlessRecoveryService;
|
|
||||||
import 'package:twonly/src/utils/keyvalue.dart';
|
import 'package:twonly/src/utils/keyvalue.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/pow.dart';
|
import 'package:twonly/src/utils/pow.dart';
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,54 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream<int> watchUnverifiedGroupMembersCount(String groupId) {
|
||||||
|
final gm = groupMembers;
|
||||||
|
final directKv = alias(keyVerifications, 'directKv');
|
||||||
|
final ur = userDiscoveryUserRelations;
|
||||||
|
final verifierKv = alias(keyVerifications, 'verifierKv');
|
||||||
|
|
||||||
|
final query = select(gm).join([
|
||||||
|
leftOuterJoin(directKv, directKv.contactId.equalsExp(gm.contactId)),
|
||||||
|
leftOuterJoin(
|
||||||
|
ur,
|
||||||
|
ur.announcedUserId.equalsExp(gm.contactId) &
|
||||||
|
ur.publicKeyVerifiedTimestamp.isNotNull() &
|
||||||
|
ur.fromContactId.equalsExp(gm.contactId).not(),
|
||||||
|
),
|
||||||
|
leftOuterJoin(
|
||||||
|
verifierKv,
|
||||||
|
verifierKv.contactId.equalsExp(ur.fromContactId),
|
||||||
|
),
|
||||||
|
])..where(gm.groupId.equals(groupId));
|
||||||
|
|
||||||
|
return query.watch().map((rows) {
|
||||||
|
if (rows.isEmpty) return 0;
|
||||||
|
|
||||||
|
final memberTrustMap = <int, ({bool direct, bool partial})>{};
|
||||||
|
|
||||||
|
for (final row in rows) {
|
||||||
|
final contactId = row.readTable(gm).contactId;
|
||||||
|
final isDirect = row.readTableOrNull(directKv) != null;
|
||||||
|
final isPartial = row.readTableOrNull(verifierKv) != null;
|
||||||
|
|
||||||
|
final current =
|
||||||
|
memberTrustMap[contactId] ?? (direct: false, partial: false);
|
||||||
|
memberTrustMap[contactId] = (
|
||||||
|
direct: current.direct || isDirect,
|
||||||
|
partial: current.partial || isPartial,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
for (final trust in memberTrustMap.values) {
|
||||||
|
if (!trust.direct && !trust.partial) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> addKeyVerification(
|
Future<void> addKeyVerification(
|
||||||
int contactId,
|
int contactId,
|
||||||
VerificationType type, {
|
VerificationType type, {
|
||||||
|
|
|
||||||
|
|
@ -896,18 +896,48 @@ abstract class AppLocalizations {
|
||||||
/// **'Verify contacts'**
|
/// **'Verify contacts'**
|
||||||
String get contactVerifyNumberTitle;
|
String get contactVerifyNumberTitle;
|
||||||
|
|
||||||
|
/// No description provided for @verifyUserIdentity.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Verify {username}\'s identity'**
|
||||||
|
String verifyUserIdentity(Object username);
|
||||||
|
|
||||||
/// No description provided for @contactVerifyNumberSubtitle.
|
/// No description provided for @contactVerifyNumberSubtitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Verify the identity of your contacts to make sure you are texting the right person.'**
|
/// **'Verify the identity of your contacts to make sure you are texting the right person.'**
|
||||||
String get contactVerifyNumberSubtitle;
|
String get contactVerifyNumberSubtitle;
|
||||||
|
|
||||||
|
/// No description provided for @inChatContactNotVerified.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Contact not verified.'**
|
||||||
|
String get inChatContactNotVerified;
|
||||||
|
|
||||||
|
/// No description provided for @groupMembersNotVerified.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count} members are not verified.'**
|
||||||
|
String groupMembersNotVerified(Object count);
|
||||||
|
|
||||||
/// No description provided for @userVerifiedTitle.
|
/// No description provided for @userVerifiedTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Contact verified'**
|
/// **'Contact verified'**
|
||||||
String get userVerifiedTitle;
|
String get userVerifiedTitle;
|
||||||
|
|
||||||
|
/// No description provided for @scanUserQrCode.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Scan {username}\'s QR code'**
|
||||||
|
String scanUserQrCode(Object username);
|
||||||
|
|
||||||
|
/// No description provided for @openOwnQrCode.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Open your own QR code'**
|
||||||
|
String get openOwnQrCode;
|
||||||
|
|
||||||
/// No description provided for @contactVerifiedBy.
|
/// No description provided for @contactVerifiedBy.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -2384,6 +2414,12 @@ abstract class AppLocalizations {
|
||||||
/// **'Let a friend scan this QR code to add you'**
|
/// **'Let a friend scan this QR code to add you'**
|
||||||
String get addContactQrSheetSubtext;
|
String get addContactQrSheetSubtext;
|
||||||
|
|
||||||
|
/// No description provided for @letUserScanQrCode.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Let {username} scan this QR code'**
|
||||||
|
String letUserScanQrCode(Object username);
|
||||||
|
|
||||||
/// No description provided for @finishSetupCardTitle.
|
/// No description provided for @finishSetupCardTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
@ -2741,7 +2777,7 @@ abstract class AppLocalizations {
|
||||||
/// No description provided for @verificationBadgeGeneralDesc.
|
/// No description provided for @verificationBadgeGeneralDesc.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'The badge *protects you from scammers and attackers*. It will be displayed next to a contact that has been *manually verified* by you or a friend.'**
|
/// **'The badge gives you the peace of mind that you are messaging the *right person* and your *messages remain confidential*.'**
|
||||||
String get verificationBadgeGeneralDesc;
|
String get verificationBadgeGeneralDesc;
|
||||||
|
|
||||||
/// No description provided for @verificationBadgeGreenDesc.
|
/// No description provided for @verificationBadgeGreenDesc.
|
||||||
|
|
@ -2768,6 +2804,18 @@ abstract class AppLocalizations {
|
||||||
/// **'Scan now'**
|
/// **'Scan now'**
|
||||||
String get scanNow;
|
String get scanNow;
|
||||||
|
|
||||||
|
/// No description provided for @qrScannerVerifyHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'To verify a contact ask them to open their QR code (Chat Lists > QR code button at the bottom right)'**
|
||||||
|
String get qrScannerVerifyHint;
|
||||||
|
|
||||||
|
/// No description provided for @qrScannerVerifyUserHint.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'To verify {username} ask them to open their QR code (Chat Lists > QR code button at the bottom right)'**
|
||||||
|
String qrScannerVerifyUserHint(Object username);
|
||||||
|
|
||||||
/// No description provided for @openQrCode.
|
/// No description provided for @openQrCode.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
||||||
|
|
@ -435,13 +435,34 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get contactVerifyNumberTitle => 'Kontakte verifizieren';
|
String get contactVerifyNumberTitle => 'Kontakte verifizieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String verifyUserIdentity(Object username) {
|
||||||
|
return 'Identität von $username verifizieren';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contactVerifyNumberSubtitle =>
|
String get contactVerifyNumberSubtitle =>
|
||||||
'Überprüfe die Identität deiner Kontakte, um sicherzugehen, dass du mit der richtigen Person schreibst.';
|
'Überprüfe die Identität deiner Kontakte, um sicherzugehen, dass du mit der richtigen Person schreibst.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get inChatContactNotVerified => 'Kontakt nicht verifiziert.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String groupMembersNotVerified(Object count) {
|
||||||
|
return '$count Mitglieder sind nicht verifiziert.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get userVerifiedTitle => 'Kontakt verifiziert';
|
String get userVerifiedTitle => 'Kontakt verifiziert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String scanUserQrCode(Object username) {
|
||||||
|
return 'QR-Code von $username scannen';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openOwnQrCode => 'Eigenen QR-Code öffnen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String contactVerifiedBy(Object username) {
|
String contactVerifiedBy(Object username) {
|
||||||
return 'Verifiziert von $username';
|
return 'Verifiziert von $username';
|
||||||
|
|
@ -1317,6 +1338,11 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
String get addContactQrSheetSubtext =>
|
String get addContactQrSheetSubtext =>
|
||||||
'Lass einen Freund diesen QR-Code scannen, um dich hinzuzufügen';
|
'Lass einen Freund diesen QR-Code scannen, um dich hinzuzufügen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String letUserScanQrCode(Object username) {
|
||||||
|
return 'Lass $username diesen QR-Code scannen';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get finishSetupCardTitle => 'Profil vervollständigen';
|
String get finishSetupCardTitle => 'Profil vervollständigen';
|
||||||
|
|
||||||
|
|
@ -1542,7 +1568,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get verificationBadgeGeneralDesc =>
|
String get verificationBadgeGeneralDesc =>
|
||||||
'Der Haken *schützt dich vor Betrügern und Angreifern*. Es wird neben einem Kontakt angezeigt, der von dir oder einem Freund *manuell überprüft* wurde.';
|
'Der Haken gibt dir die Sicherheit, dass du mit der *richtigen Person* schreibst und deine *Nachrichten vertraulich* bleiben.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get verificationBadgeGreenDesc =>
|
String get verificationBadgeGreenDesc =>
|
||||||
|
|
@ -1559,6 +1585,15 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get scanNow => 'Jetzt scannen';
|
String get scanNow => 'Jetzt scannen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get qrScannerVerifyHint =>
|
||||||
|
'Um einen Kontakt zu verifizieren, bitte ihn, seinen QR-Code zu öffnen (Chat-Liste > QR-Code-Button unten rechts)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String qrScannerVerifyUserHint(Object username) {
|
||||||
|
return 'Um $username zu verifizieren, bitte ihn, seinen QR-Code zu öffnen (Chat-Liste > QR-Code-Button unten rechts)';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get openQrCode => 'QR-Code öffnen';
|
String get openQrCode => 'QR-Code öffnen';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -431,13 +431,34 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get contactVerifyNumberTitle => 'Verify contacts';
|
String get contactVerifyNumberTitle => 'Verify contacts';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String verifyUserIdentity(Object username) {
|
||||||
|
return 'Verify $username\'s identity';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contactVerifyNumberSubtitle =>
|
String get contactVerifyNumberSubtitle =>
|
||||||
'Verify the identity of your contacts to make sure you are texting the right person.';
|
'Verify the identity of your contacts to make sure you are texting the right person.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get inChatContactNotVerified => 'Contact not verified.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String groupMembersNotVerified(Object count) {
|
||||||
|
return '$count members are not verified.';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get userVerifiedTitle => 'Contact verified';
|
String get userVerifiedTitle => 'Contact verified';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String scanUserQrCode(Object username) {
|
||||||
|
return 'Scan $username\'s QR code';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get openOwnQrCode => 'Open your own QR code';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String contactVerifiedBy(Object username) {
|
String contactVerifiedBy(Object username) {
|
||||||
return 'Verified by $username';
|
return 'Verified by $username';
|
||||||
|
|
@ -1309,6 +1330,11 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
String get addContactQrSheetSubtext =>
|
String get addContactQrSheetSubtext =>
|
||||||
'Let a friend scan this QR code to add you';
|
'Let a friend scan this QR code to add you';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String letUserScanQrCode(Object username) {
|
||||||
|
return 'Let $username scan this QR code';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get finishSetupCardTitle => 'Complete your profile';
|
String get finishSetupCardTitle => 'Complete your profile';
|
||||||
|
|
||||||
|
|
@ -1529,7 +1555,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get verificationBadgeGeneralDesc =>
|
String get verificationBadgeGeneralDesc =>
|
||||||
'The badge *protects you from scammers and attackers*. It will be displayed next to a contact that has been *manually verified* by you or a friend.';
|
'The badge gives you the peace of mind that you are messaging the *right person* and your *messages remain confidential*.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get verificationBadgeGreenDesc =>
|
String get verificationBadgeGreenDesc =>
|
||||||
|
|
@ -1546,6 +1572,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get scanNow => 'Scan now';
|
String get scanNow => 'Scan now';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get qrScannerVerifyHint =>
|
||||||
|
'To verify a contact ask them to open their QR code (Chat Lists > QR code button at the bottom right)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String qrScannerVerifyUserHint(Object username) {
|
||||||
|
return 'To verify $username ask them to open their QR code (Chat Lists > QR code button at the bottom right)';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get openQrCode => 'Open QR code';
|
String get openQrCode => 'Open QR code';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 147f44e608f39f04af3b3be9aee1ad662e5a84e1
|
Subproject commit 27b598a151a0122f05670d5d8fb6dcfd5f440127
|
||||||
|
|
@ -143,7 +143,13 @@ final routerProvider = GoRouter(
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: Routes.cameraQRScanner,
|
path: Routes.cameraQRScanner,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return const QrCodeScannerView();
|
final extra = state.extra as Map<String, dynamic>?;
|
||||||
|
final contact = extra?['contact'] as Contact?;
|
||||||
|
final openToVerify = extra?['openToVerify'] as bool? ?? false;
|
||||||
|
return QrCodeScannerView(
|
||||||
|
contact: contact,
|
||||||
|
openToVerify: openToVerify,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
@ -248,7 +254,9 @@ final routerProvider = GoRouter(
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'verifybadge',
|
path: 'verifybadge',
|
||||||
builder: (context, state) => const VerificationBadeFaqView(),
|
builder: (context, state) => VerificationBadeFaqView(
|
||||||
|
contact: state.extra as Contact?,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/utils/avatars.dart';
|
import 'package:twonly/src/utils/avatars.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/utils/qr.utils.dart';
|
import 'package:twonly/src/utils/qr.utils.dart';
|
||||||
|
|
@ -16,6 +18,55 @@ class ProfileQrCodeComp extends StatefulWidget {
|
||||||
final double size;
|
final double size;
|
||||||
final bool showAvatar;
|
final bool showAvatar;
|
||||||
|
|
||||||
|
static Future<void> showSheet(BuildContext context, {Contact? contact}) {
|
||||||
|
return showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.color.surface,
|
||||||
|
borderRadius: const BorderRadius.vertical(
|
||||||
|
top: Radius.circular(24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: double.infinity),
|
||||||
|
Container(
|
||||||
|
width: 40,
|
||||||
|
height: 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.color.onSurface.withValues(alpha: 0.2),
|
||||||
|
borderRadius: BorderRadius.circular(2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
contact != null
|
||||||
|
? context.lang.letUserScanQrCode(
|
||||||
|
getContactDisplayName(contact),
|
||||||
|
)
|
||||||
|
: context.lang.addContactQrSheetSubtext,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: context.color.onSurface.withValues(alpha: 0.6),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const ProfileQrCodeComp(),
|
||||||
|
const SizedBox(height: 34),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ProfileQrCodeComp> createState() => _ProfileQrCodeCompState();
|
State<ProfileQrCodeComp> createState() => _ProfileQrCodeCompState();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/profile.service.dart';
|
import 'package:twonly/src/services/profile.service.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/components/profile_qr_code.comp.dart';
|
||||||
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||||
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
import 'package:twonly/src/visual/elements/svg_icon.element.dart';
|
||||||
import 'package:twonly/src/visual/themes/light.dart';
|
import 'package:twonly/src/visual/themes/light.dart';
|
||||||
|
|
@ -15,12 +18,60 @@ const colorVerificationBadgeYellow = Color.fromARGB(255, 0, 182, 238);
|
||||||
class VerificationBadgeInfo extends StatelessWidget {
|
class VerificationBadgeInfo extends StatelessWidget {
|
||||||
const VerificationBadgeInfo({
|
const VerificationBadgeInfo({
|
||||||
this.displayButtons = false,
|
this.displayButtons = false,
|
||||||
|
this.contact,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final bool displayButtons;
|
final bool displayButtons;
|
||||||
|
final Contact? contact;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final scanButton = MyButton(
|
||||||
|
variant: MyButtonVariant.primaryDense,
|
||||||
|
onPressed: () => context.push(
|
||||||
|
Routes.cameraQRScanner,
|
||||||
|
extra: contact != null
|
||||||
|
? {
|
||||||
|
'contact': contact,
|
||||||
|
'openToVerify': true,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const FaIcon(FontAwesomeIcons.camera),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
contact != null
|
||||||
|
? context.lang.scanUserQrCode(
|
||||||
|
getContactDisplayName(contact!),
|
||||||
|
)
|
||||||
|
: context.lang.scanNow,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final openButton = MyButton(
|
||||||
|
variant: MyButtonVariant.primaryDense,
|
||||||
|
onPressed: () => ProfileQrCodeComp.showSheet(context, contact: contact),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const FaIcon(FontAwesomeIcons.qrcode),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
contact != null
|
||||||
|
? context.lang.openOwnQrCode
|
||||||
|
: context.lang.openQrCode,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
RichText(
|
RichText(
|
||||||
|
|
@ -40,43 +91,36 @@ class VerificationBadgeInfo extends StatelessWidget {
|
||||||
icon: const SvgIcon(assetPath: SvgIcons.verifiedGreen, size: 40),
|
icon: const SvgIcon(assetPath: SvgIcons.verifiedGreen, size: 40),
|
||||||
description: context.lang.verificationBadgeGreenDesc,
|
description: context.lang.verificationBadgeGreenDesc,
|
||||||
boldTextColor: primaryColor,
|
boldTextColor: primaryColor,
|
||||||
onTap: () => context.push(Routes.cameraQRScanner),
|
onTap: () => context.push(
|
||||||
|
Routes.cameraQRScanner,
|
||||||
|
extra: contact != null
|
||||||
|
? {
|
||||||
|
'contact': contact,
|
||||||
|
'openToVerify': true,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (displayButtons)
|
if (displayButtons)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 20),
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
child: Row(
|
child: contact != null
|
||||||
mainAxisSize: MainAxisSize.min,
|
? Column(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
IntrinsicWidth(
|
children: [
|
||||||
child: MyButton(
|
scanButton,
|
||||||
variant: MyButtonVariant.primaryDense,
|
const SizedBox(height: 8),
|
||||||
onPressed: () => context.push(Routes.cameraQRScanner),
|
openButton,
|
||||||
child: Row(
|
],
|
||||||
children: [
|
)
|
||||||
const FaIcon(FontAwesomeIcons.camera),
|
: Row(
|
||||||
const SizedBox(width: 6),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Text(context.lang.scanNow),
|
children: [
|
||||||
],
|
IntrinsicWidth(child: scanButton),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
|
IntrinsicWidth(child: openButton),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
IntrinsicWidth(
|
|
||||||
child: MyButton(
|
|
||||||
variant: MyButtonVariant.primaryDense,
|
|
||||||
onPressed: () => context.push(Routes.settingsPublicProfile),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const FaIcon(FontAwesomeIcons.qrcode),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(context.lang.openQrCode),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (userService.currentUser.securityProfile != SecurityProfile.strict ||
|
if (userService.currentUser.securityProfile != SecurityProfile.strict ||
|
||||||
userService.currentUser.isUserDiscoveryEnabled)
|
userService.currentUser.isUserDiscoveryEnabled)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,23 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/camera_preview_controller_view.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
import 'package:twonly/src/visual/views/camera/camera_preview_components/main_camera_controller.dart';
|
||||||
|
|
||||||
class QrCodeScannerView extends StatefulWidget {
|
class QrCodeScannerView extends StatefulWidget {
|
||||||
const QrCodeScannerView({super.key});
|
const QrCodeScannerView({
|
||||||
|
this.contact,
|
||||||
|
this.openToVerify = false,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Contact? contact;
|
||||||
|
final bool openToVerify;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<QrCodeScannerView> createState() => QrCodeScannerViewState();
|
State<QrCodeScannerView> createState() => QrCodeScannerViewState();
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +67,31 @@ class QrCodeScannerViewState extends State<QrCodeScannerView> {
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (widget.openToVerify)
|
||||||
|
Positioned(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
bottom: MediaQuery.paddingOf(context).bottom + 20,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withValues(alpha: 0.7),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
widget.contact != null
|
||||||
|
? context.lang.qrScannerVerifyUserHint(
|
||||||
|
getContactDisplayName(widget.contact!),
|
||||||
|
)
|
||||||
|
: context.lang.qrScannerVerifyHint,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import 'package:twonly/src/visual/views/chats/chat_messages_components/blink.com
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_group_action.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_group_action.dart';
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_list_entry.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_list_entry.dart';
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_date_chip.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_date_chip.dart';
|
||||||
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/in_chat_group_overview.dart';
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_input.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_input.dart';
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/response_container.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/response_container.dart';
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/typing_indicator.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/typing_indicator.dart';
|
||||||
|
|
@ -89,15 +90,6 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
||||||
|
|
||||||
Mutex protectMessageUpdating = Mutex();
|
Mutex protectMessageUpdating = Mutex();
|
||||||
|
|
||||||
// @override
|
|
||||||
// void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
// if (state == AppLifecycleState.resumed) {
|
|
||||||
// protectMessageUpdating.protect(() async {
|
|
||||||
// await setMessages(allMessages, groupActions);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
bool _isViewActive() {
|
bool _isViewActive() {
|
||||||
if (!mounted) return false;
|
if (!mounted) return false;
|
||||||
return !AppState.isAppInBackground &&
|
return !AppState.isAppInBackground &&
|
||||||
|
|
@ -326,11 +318,14 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ScrollablePositionedList.builder(
|
child: Align(
|
||||||
reverse: true,
|
alignment: Alignment.topCenter,
|
||||||
itemCount: messages.length + 1 + 1,
|
child: ScrollablePositionedList.builder(
|
||||||
itemScrollController: itemScrollController,
|
shrinkWrap: true,
|
||||||
itemBuilder: (context, i) {
|
reverse: true,
|
||||||
|
itemCount: messages.length + 1 + 1,
|
||||||
|
itemScrollController: itemScrollController,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
return userService.currentUser.typingIndicators
|
return userService.currentUser.typingIndicators
|
||||||
? TypingIndicator(group: group)
|
? TypingIndicator(group: group)
|
||||||
|
|
@ -338,8 +333,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
||||||
}
|
}
|
||||||
i -= 1;
|
i -= 1;
|
||||||
if (i == messages.length) {
|
if (i == messages.length) {
|
||||||
return const Padding(
|
return Padding(
|
||||||
padding: EdgeInsetsGeometry.only(top: 10),
|
padding: const EdgeInsets.only(top: 10),
|
||||||
|
child: InChatGroupOverview(group: group),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (messages[i].isDate) {
|
if (messages[i].isDate) {
|
||||||
|
|
@ -386,6 +382,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (quotesMessage != null)
|
if (quotesMessage != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:twonly/locator.dart';
|
||||||
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
|
import 'package:twonly/src/database/daos/key_verification.dao.dart';
|
||||||
|
import 'package:twonly/src/database/tables/contacts.table.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||||
|
import 'package:twonly/src/visual/elements/my_button.element.dart';
|
||||||
|
|
||||||
|
class InChatGroupOverview extends StatefulWidget {
|
||||||
|
const InChatGroupOverview({
|
||||||
|
required this.group,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Group group;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InChatGroupOverview> createState() => _InChatGroupOverviewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InChatGroupOverviewState extends State<InChatGroupOverview> {
|
||||||
|
Contact? _directContact;
|
||||||
|
StreamSubscription<dynamic>? _verificationSub;
|
||||||
|
StreamSubscription<int>? _unverifiedCountSub;
|
||||||
|
bool _isVerified = true;
|
||||||
|
int _unverifiedCount = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initVerificationCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initVerificationCheck() async {
|
||||||
|
if (widget.group.isDirectChat) {
|
||||||
|
final contacts = await twonlyDB.groupsDao.getGroupContact(
|
||||||
|
widget.group.groupId,
|
||||||
|
);
|
||||||
|
if (contacts.isNotEmpty) {
|
||||||
|
_directContact = contacts.first;
|
||||||
|
_verificationSub = twonlyDB.keyVerificationDao
|
||||||
|
.watchContactVerification(_directContact!.userId)
|
||||||
|
.listen((verifications) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isVerified = verifications.any(
|
||||||
|
(v) =>
|
||||||
|
v.$1.type != VerificationType.contactSharedByVerified,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_verificationSub = twonlyDB.keyVerificationDao
|
||||||
|
.watchAllGroupMembersVerified(widget.group.groupId)
|
||||||
|
.listen((status) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isVerified = status == VerificationStatus.trusted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_unverifiedCountSub = twonlyDB.keyVerificationDao
|
||||||
|
.watchUnverifiedGroupMembersCount(widget.group.groupId)
|
||||||
|
.listen((count) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_unverifiedCount = count;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_verificationSub?.cancel();
|
||||||
|
_unverifiedCountSub?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onVerifyPressed() {
|
||||||
|
if (widget.group.isDirectChat && _directContact != null) {
|
||||||
|
context.push(
|
||||||
|
Routes.settingsHelpFaqVerifyBadge,
|
||||||
|
extra: _directContact,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
context.push(Routes.profileGroup(widget.group.groupId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
||||||
|
child: Center(
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(top: 40),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 250),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.outlineVariant,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 56,
|
||||||
|
left: 14,
|
||||||
|
right: 14,
|
||||||
|
bottom: 14,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
widget.group.groupName,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
VerificationBadgeComp(
|
||||||
|
group: widget.group,
|
||||||
|
size: 20,
|
||||||
|
clickable: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!_isVerified) ...[
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Card(
|
||||||
|
elevation: 0,
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.error.withValues(alpha: 0.1),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
horizontal: 14,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.group.isDirectChat
|
||||||
|
? context.lang.inChatContactNotVerified
|
||||||
|
: context.lang.groupMembersNotVerified(
|
||||||
|
_unverifiedCount,
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onErrorContainer,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
MyButton(
|
||||||
|
variant: MyButtonVariant.secondaryDense,
|
||||||
|
onPressed: _onVerifyPressed,
|
||||||
|
child: Text(context.lang.unverifiedWarningButton),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
child: AvatarIcon(group: widget.group, fontSize: 40),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -102,52 +102,6 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
await SharePlus.instance.share(params);
|
await SharePlus.instance.share(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showMyQrCode() {
|
|
||||||
// ignore: inference_failure_on_function_invocation
|
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
isScrollControlled: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
builder: (context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: context.color.surface,
|
|
||||||
borderRadius: const BorderRadius.vertical(
|
|
||||||
top: Radius.circular(24),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const SizedBox(width: double.infinity),
|
|
||||||
Container(
|
|
||||||
width: 40,
|
|
||||||
height: 4,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: context.color.onSurface.withValues(alpha: 0.2),
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
const ProfileQrCodeComp(),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Text(
|
|
||||||
context.lang.addContactQrSheetSubtext,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: context.color.onSurface.withValues(alpha: 0.6),
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_contactsStream.cancel();
|
_contactsStream.cancel();
|
||||||
|
|
@ -344,7 +298,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MyButton(
|
child: MyButton(
|
||||||
variant: MyButtonVariant.secondaryDense,
|
variant: MyButtonVariant.secondaryDense,
|
||||||
onPressed: _showMyQrCode,
|
onPressed: () => ProfileQrCodeComp.showSheet(context),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -93,18 +93,25 @@ class _VerificationExpansionTileCompState
|
||||||
contact: widget.contact,
|
contact: widget.contact,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
text: context.lang.contactVerifyNumberTitle,
|
text: context.lang.verifyUserIdentity(
|
||||||
|
getContactDisplayName(widget.contact),
|
||||||
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await context.push(Routes.settingsHelpFaqVerifyBadge);
|
await context.push(
|
||||||
|
Routes.settingsHelpFaqVerifyBadge,
|
||||||
|
extra: widget.contact,
|
||||||
|
);
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final sharedVerifierIds = _keyVerifications
|
final sharedVerifierIds = _keyVerifications
|
||||||
.where((pair) =>
|
.where(
|
||||||
pair.$1.type == VerificationType.contactSharedByVerified &&
|
(pair) =>
|
||||||
pair.$1.verifiedBy != null)
|
pair.$1.type == VerificationType.contactSharedByVerified &&
|
||||||
|
pair.$1.verifiedBy != null,
|
||||||
|
)
|
||||||
.map((pair) => pair.$1.verifiedBy!)
|
.map((pair) => pair.$1.verifiedBy!)
|
||||||
.toSet();
|
.toSet();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,11 @@ import 'package:twonly/src/visual/views/onboarding/setup/profile.setup.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/profile_selection.setup.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup/profile_selection.setup.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/security_profile.setup.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup/security_profile.setup.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/share_your_friends.setup.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup/share_your_friends.setup.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/verification_badge.setup.dart';
|
|
||||||
import 'package:twonly/src/visual/views/settings/privacy/user_discovery/components/user_discovery_setup.comp.dart';
|
import 'package:twonly/src/visual/views/settings/privacy/user_discovery/components/user_discovery_setup.comp.dart';
|
||||||
|
|
||||||
enum SetupPages {
|
enum SetupPages {
|
||||||
profile,
|
profile,
|
||||||
backup,
|
backup,
|
||||||
verificationBadge,
|
|
||||||
profileSelection,
|
profileSelection,
|
||||||
securityProfile,
|
securityProfile,
|
||||||
shareYourFriends,
|
shareYourFriends,
|
||||||
|
|
@ -40,21 +38,18 @@ extension SetupPagesExtension on SetupPages {
|
||||||
return [
|
return [
|
||||||
SetupPages.profile,
|
SetupPages.profile,
|
||||||
SetupPages.backup,
|
SetupPages.backup,
|
||||||
SetupPages.verificationBadge,
|
|
||||||
SetupPages.profileSelection,
|
SetupPages.profileSelection,
|
||||||
];
|
];
|
||||||
case SetupProfile.maximum:
|
case SetupProfile.maximum:
|
||||||
return [
|
return [
|
||||||
SetupPages.profile,
|
SetupPages.profile,
|
||||||
SetupPages.backup,
|
SetupPages.backup,
|
||||||
SetupPages.verificationBadge,
|
|
||||||
SetupPages.profileSelection,
|
SetupPages.profileSelection,
|
||||||
];
|
];
|
||||||
case SetupProfile.customized:
|
case SetupProfile.customized:
|
||||||
return [
|
return [
|
||||||
SetupPages.profile,
|
SetupPages.profile,
|
||||||
SetupPages.backup,
|
SetupPages.backup,
|
||||||
SetupPages.verificationBadge,
|
|
||||||
SetupPages.profileSelection,
|
SetupPages.profileSelection,
|
||||||
SetupPages.securityProfile,
|
SetupPages.securityProfile,
|
||||||
SetupPages.shareYourFriends,
|
SetupPages.shareYourFriends,
|
||||||
|
|
@ -225,8 +220,6 @@ class _SetupViewState extends State<SetupView> {
|
||||||
return const ProfileSelectionSetup();
|
return const ProfileSelectionSetup();
|
||||||
case SetupPages.securityProfile:
|
case SetupPages.securityProfile:
|
||||||
return const SecurityProfileSetup();
|
return const SecurityProfileSetup();
|
||||||
case SetupPages.verificationBadge:
|
|
||||||
return const VerificationBadgeSetupPage();
|
|
||||||
case SetupPages.shareYourFriends:
|
case SetupPages.shareYourFriends:
|
||||||
return ShareYourFriendsSetupPage(state: state);
|
return ShareYourFriendsSetupPage(state: state);
|
||||||
case SetupPages.letYourFriendsFindYou:
|
case SetupPages.letYourFriendsFindYou:
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
|
||||||
import 'package:twonly/src/visual/components/verification_badge_info.comp.dart';
|
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.dart';
|
|
||||||
|
|
||||||
class VerificationBadgeSetupPage extends StatelessWidget {
|
|
||||||
const VerificationBadgeSetupPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
context.lang.onboardingVerificationBadgeTitle,
|
|
||||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
const VerificationBadgeInfo(),
|
|
||||||
const SizedBox(height: 60),
|
|
||||||
const NextButtonComp(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -83,7 +83,12 @@ class _PublicProfileViewState extends State<PublicProfileView> {
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
leading: const FaIcon(FontAwesomeIcons.qrcode),
|
leading: const FaIcon(FontAwesomeIcons.qrcode),
|
||||||
text: context.lang.scanOtherProfile,
|
text: context.lang.scanOtherProfile,
|
||||||
onTap: () => context.push(Routes.cameraQRScanner),
|
onTap: () => context.push(
|
||||||
|
Routes.cameraQRScanner,
|
||||||
|
extra: {
|
||||||
|
'openToVerify': true,
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
BetterListTile(
|
BetterListTile(
|
||||||
leading: const FaIcon(
|
leading: const FaIcon(
|
||||||
|
|
@ -98,7 +103,8 @@ class _PublicProfileViewState extends State<PublicProfileView> {
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final params = ShareParams(
|
final params = ShareParams(
|
||||||
text: 'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(_publicKey!)}',
|
text:
|
||||||
|
'https://me.twonly.eu/${userService.currentUser.username}#${base64Url.encode(_publicKey!)}',
|
||||||
);
|
);
|
||||||
SharePlus.instance.share(params);
|
SharePlus.instance.share(params);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/components/verification_badge_info.comp.dart';
|
import 'package:twonly/src/visual/components/verification_badge_info.comp.dart';
|
||||||
|
|
||||||
class VerificationBadeFaqView extends StatefulWidget {
|
class VerificationBadeFaqView extends StatefulWidget {
|
||||||
const VerificationBadeFaqView({super.key});
|
const VerificationBadeFaqView({super.key, this.contact});
|
||||||
|
|
||||||
|
final Contact? contact;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<VerificationBadeFaqView> createState() =>
|
State<VerificationBadeFaqView> createState() =>
|
||||||
|
|
@ -19,9 +22,10 @@ class _VerificationBadeFaqViewState extends State<VerificationBadeFaqView> {
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
padding: const EdgeInsets.all(40),
|
padding: const EdgeInsets.all(40),
|
||||||
children: const [
|
children: [
|
||||||
VerificationBadgeInfo(
|
VerificationBadgeInfo(
|
||||||
displayButtons: true,
|
displayButtons: true,
|
||||||
|
contact: widget.contact,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue