mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 15:12:13 +00:00
finish 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
1ad304ec2e
commit
00cb615e56
9 changed files with 564 additions and 394 deletions
|
|
@ -3469,6 +3469,30 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Ask a friend'**
|
/// **'Ask a friend'**
|
||||||
String get replyAskAFriend;
|
String get replyAskAFriend;
|
||||||
|
|
||||||
|
/// No description provided for @unverifiedWarningDirectTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Identity not verified in person'**
|
||||||
|
String get unverifiedWarningDirectTitle;
|
||||||
|
|
||||||
|
/// No description provided for @unverifiedWarningGroupTitle.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Not all members are verified in person'**
|
||||||
|
String get unverifiedWarningGroupTitle;
|
||||||
|
|
||||||
|
/// No description provided for @unverifiedWarningBody.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'*Avoid sharing sensitive data*. Risk of *impersonation* without manual verification.'**
|
||||||
|
String get unverifiedWarningBody;
|
||||||
|
|
||||||
|
/// No description provided for @unverifiedWarningButton.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Verify now'**
|
||||||
|
String get unverifiedWarningButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1973,4 +1973,19 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get replyAskAFriend => 'Einen Freund fragen';
|
String get replyAskAFriend => 'Einen Freund fragen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unverifiedWarningDirectTitle =>
|
||||||
|
'Identität nicht persönlich verifiziert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unverifiedWarningGroupTitle =>
|
||||||
|
'Nicht alle Mitglieder sind persönlich verifiziert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unverifiedWarningBody =>
|
||||||
|
'*Teile keine geheimen Daten*. Jemand könnte sich *als dein Freund ausgeben*.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unverifiedWarningButton => 'Jetzt verifizieren';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1958,4 +1958,18 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get replyAskAFriend => 'Ask a friend';
|
String get replyAskAFriend => 'Ask a friend';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unverifiedWarningDirectTitle => 'Identity not verified in person';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unverifiedWarningGroupTitle =>
|
||||||
|
'Not all members are verified in person';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unverifiedWarningBody =>
|
||||||
|
'*Avoid sharing sensitive data*. Risk of *impersonation* without manual verification.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unverifiedWarningButton => 'Verify now';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 5f60fcc10450b40f75b3c170d27caa4398d84e4a
|
Subproject commit c33a4c3be99b38596abd0cfa91333db3a340dee2
|
||||||
|
|
@ -4,4 +4,5 @@ enum SecurityProfile { normal, strict }
|
||||||
|
|
||||||
extension SecurityProfileExtension on SecurityProfile {
|
extension SecurityProfileExtension on SecurityProfile {
|
||||||
bool get showWarningForNonVerifiedContacts => this == SecurityProfile.strict;
|
bool get showWarningForNonVerifiedContacts => this == SecurityProfile.strict;
|
||||||
|
bool get showOnlyVerifiedInChatViewList => this == SecurityProfile.normal;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -293,9 +293,10 @@ Future<List<int>> sha256File(File file) async {
|
||||||
List<TextSpan> formattedText(
|
List<TextSpan> formattedText(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String input, {
|
String input, {
|
||||||
|
Color? textColor,
|
||||||
Color? boldTextColor,
|
Color? boldTextColor,
|
||||||
}) {
|
}) {
|
||||||
final defaultColor = Theme.of(context).colorScheme.onSurface;
|
final defaultColor = textColor ?? Theme.of(context).colorScheme.onSurface;
|
||||||
|
|
||||||
final regex = RegExp(r'\*(.*?)\*');
|
final regex = RegExp(r'\*(.*?)\*');
|
||||||
final spans = <TextSpan>[];
|
final spans = <TextSpan>[];
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
import 'package:twonly/src/services/api/mediafiles/download.api.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/avatar_icon.comp.dart';
|
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||||
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
import 'package:twonly/src/visual/components/flame_counter.comp.dart';
|
||||||
|
|
@ -73,26 +74,20 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
_lastReactionStream = twonlyDB.reactionsDao
|
_lastReactionStream = twonlyDB.reactionsDao.watchLastReactions(widget.group.groupId).listen((update) {
|
||||||
.watchLastReactions(widget.group.groupId)
|
|
||||||
.listen((update) {
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_lastReaction = update;
|
_lastReaction = update;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
_messagesNotOpenedStream = twonlyDB.messagesDao
|
_messagesNotOpenedStream = twonlyDB.messagesDao.watchMessageNotOpened(widget.group.groupId).listen((update) {
|
||||||
.watchMessageNotOpened(widget.group.groupId)
|
|
||||||
.listen((update) {
|
|
||||||
protectUpdateState.protect(() async {
|
protectUpdateState.protect(() async {
|
||||||
await updateState(_lastMessage, update);
|
await updateState(_lastMessage, update);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
_lastMediaFilesStream = twonlyDB.mediaFilesDao
|
_lastMediaFilesStream = twonlyDB.mediaFilesDao.watchNewestMediaFiles().listen((mediaFiles) {
|
||||||
.watchNewestMediaFiles()
|
|
||||||
.listen((mediaFiles) {
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
for (final mediaFile in mediaFiles) {
|
for (final mediaFile in mediaFiles) {
|
||||||
final index = _previewMediaFiles.indexWhere(
|
final index = _previewMediaFiles.indexWhere(
|
||||||
|
|
@ -125,9 +120,7 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
_previewMessages = [];
|
_previewMessages = [];
|
||||||
} else if (newMessagesNotOpened.isNotEmpty) {
|
} else if (newMessagesNotOpened.isNotEmpty) {
|
||||||
// Filter for the preview non opened messages. First messages which where send but not yet opened by the other side.
|
// Filter for the preview non opened messages. First messages which where send but not yet opened by the other side.
|
||||||
final receivedMessages = newMessagesNotOpened
|
final receivedMessages = newMessagesNotOpened.where((x) => x.senderId != null).toList();
|
||||||
.where((x) => x.senderId != null)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (receivedMessages.isNotEmpty) {
|
if (receivedMessages.isNotEmpty) {
|
||||||
_previewMessages = receivedMessages;
|
_previewMessages = receivedMessages;
|
||||||
|
|
@ -151,9 +144,7 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final msgs = _previewMessages
|
final msgs = _previewMessages.where((x) => x.type == MessageType.media.name).toList();
|
||||||
.where((x) => x.type == MessageType.media.name)
|
|
||||||
.toList();
|
|
||||||
if (msgs.isNotEmpty &&
|
if (msgs.isNotEmpty &&
|
||||||
msgs.first.type == MessageType.media.name &&
|
msgs.first.type == MessageType.media.name &&
|
||||||
!msgs.first.isDeletedFromSender &&
|
!msgs.first.isDeletedFromSender &&
|
||||||
|
|
@ -165,8 +156,7 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final message in _previewMessages) {
|
for (final message in _previewMessages) {
|
||||||
if (message.mediaId != null &&
|
if (message.mediaId != null && !_previewMediaFiles.any((t) => t.mediaId == message.mediaId)) {
|
||||||
!_previewMediaFiles.any((t) => t.mediaId == message.mediaId)) {
|
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||||
message.mediaId!,
|
message.mediaId!,
|
||||||
);
|
);
|
||||||
|
|
@ -191,9 +181,7 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_hasNonOpenedMediaFile) {
|
if (_hasNonOpenedMediaFile) {
|
||||||
final msgs = _previewMessages
|
final msgs = _previewMessages.where((x) => x.type == MessageType.media.name).toList();
|
||||||
.where((x) => x.type == MessageType.media.name)
|
|
||||||
.toList();
|
|
||||||
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
final mediaFile = await twonlyDB.mediaFilesDao.getMediaFileById(
|
||||||
msgs.first.mediaId!,
|
msgs.first.mediaId!,
|
||||||
);
|
);
|
||||||
|
|
@ -219,6 +207,9 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder<void>(
|
||||||
|
stream: userService.onUserUpdated,
|
||||||
|
builder: (context, snapshot) {
|
||||||
return GroupContextMenu(
|
return GroupContextMenu(
|
||||||
group: widget.group,
|
group: widget.group,
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
|
@ -230,7 +221,7 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
const SizedBox(width: 3),
|
const SizedBox(width: 3),
|
||||||
VerificationBadgeComp(
|
VerificationBadgeComp(
|
||||||
group: widget.group,
|
group: widget.group,
|
||||||
showOnlyIfVerified: true,
|
showOnlyIfVerified: userService.currentUser.securityProfile.showOnlyVerifiedInChatViewList,
|
||||||
clickable: false,
|
clickable: false,
|
||||||
size: 12,
|
size: 12,
|
||||||
),
|
),
|
||||||
|
|
@ -265,8 +256,7 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
),
|
),
|
||||||
const Text('•'),
|
const Text('•'),
|
||||||
const SizedBox(width: 5),
|
const SizedBox(width: 5),
|
||||||
if (_currentMessage != null)
|
if (_currentMessage != null) LastMessageTimeComp(message: _currentMessage),
|
||||||
LastMessageTimeComp(message: _currentMessage),
|
|
||||||
FlameCounterWidget(
|
FlameCounterWidget(
|
||||||
groupId: widget.group.groupId,
|
groupId: widget.group.groupId,
|
||||||
prefix: true,
|
prefix: true,
|
||||||
|
|
@ -302,14 +292,14 @@ class _UserListItem extends State<GroupListItemComp> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: FaIcon(
|
icon: FaIcon(
|
||||||
_hasNonOpenedMediaFile
|
_hasNonOpenedMediaFile ? FontAwesomeIcons.solidComments : FontAwesomeIcons.camera,
|
||||||
? FontAwesomeIcons.solidComments
|
|
||||||
: FontAwesomeIcons.camera,
|
|
||||||
color: context.color.outline.withAlpha(150),
|
color: context.color.outline.withAlpha(150),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart';
|
import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart';
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart';
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart';
|
||||||
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/unverified_contact_warning.comp.dart';
|
||||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/user_discovery_manual_approval.comp.dart';
|
import 'package:twonly/src/visual/views/chats/chat_messages_components/user_discovery_manual_approval.comp.dart';
|
||||||
import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart';
|
import 'package:twonly/src/visual/views/contact/contact_components/restore_flame.comp.dart';
|
||||||
|
|
||||||
|
|
@ -236,11 +237,13 @@ class _MessageInputState extends State<MessageInput> {
|
||||||
flameOnRightSide: true,
|
flameOnRightSide: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
UnverifiedContactWarningComp(
|
||||||
|
group: widget.group,
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
bottom: 10,
|
bottom: 10,
|
||||||
left: 10,
|
left: 10,
|
||||||
top: 10,
|
top: 5,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -519,6 +522,7 @@ class _MessageInputState extends State<MessageInput> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Offstage(
|
Offstage(
|
||||||
offstage: !_emojiShowing,
|
offstage: !_emojiShowing,
|
||||||
child: EmojiPicker(
|
child: EmojiPicker(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
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/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/services/profile.service.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||||
|
|
||||||
|
class UnverifiedContactWarningComp extends StatelessWidget {
|
||||||
|
const UnverifiedContactWarningComp({
|
||||||
|
required this.group,
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Group group;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder<void>(
|
||||||
|
stream: userService.onUserUpdated,
|
||||||
|
builder: (context, _) {
|
||||||
|
if (!userService.currentUser.securityProfile.showWarningForNonVerifiedContacts) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
return StreamBuilder<VerificationStatus>(
|
||||||
|
stream: twonlyDB.keyVerificationDao.watchAllGroupMembersVerified(group.groupId),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final status = snapshot.data;
|
||||||
|
if (status == null || status == VerificationStatus.trusted) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(top: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.color.errorContainer.withValues(alpha: 0.5),
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
border: Border.all(color: context.color.error.withValues(alpha: 0.5)),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 10, 12, 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
VerificationBadgeComp(
|
||||||
|
group: group,
|
||||||
|
size: 24,
|
||||||
|
clickable: false,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
group.isDirectChat
|
||||||
|
? context.lang.unverifiedWarningDirectTitle
|
||||||
|
: context.lang.unverifiedWarningGroupTitle,
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.color.onErrorContainer,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 13,
|
||||||
|
height: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.color.onErrorContainer,
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
children: formattedText(
|
||||||
|
context,
|
||||||
|
context.lang.unverifiedWarningBody,
|
||||||
|
textColor: context.color.onErrorContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
SizedBox(
|
||||||
|
height: 30,
|
||||||
|
child: FilledButton.tonal(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: context.color.onErrorContainer,
|
||||||
|
foregroundColor: context.color.errorContainer,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
textStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
if (group.isDirectChat) {
|
||||||
|
await context.push(Routes.settingsHelpFaqVerifyBadge);
|
||||||
|
} else {
|
||||||
|
await context.push(Routes.profileGroup(group.groupId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(context.lang.unverifiedWarningButton),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue