mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-12-24 22:42:33 +01:00
Add Cross-Signing
This commit is contained in:
parent
44bbb25f6e
commit
4550686829
@ -4,9 +4,13 @@
|
||||
- Chat app bar transparent
|
||||
- Implement web file picker
|
||||
- Minor design and UX improvements
|
||||
- Implement Cross Signing
|
||||
- Restore keys from online key backup
|
||||
### Changes:
|
||||
- Show presences of users sharing a direct chat
|
||||
- Big refactoring
|
||||
### Fixes:
|
||||
- Various fixes, including e2ee fixes and olm session recovery
|
||||
|
||||
# Version 0.14.0 - 2020-05-20
|
||||
### Features:
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:bubble/bubble.dart';
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/encryption.dart';
|
||||
import 'package:fluffychat/components/dialogs/simple_dialogs.dart';
|
||||
import 'package:fluffychat/components/message_content.dart';
|
||||
import 'package:fluffychat/components/reply_content.dart';
|
||||
@ -122,7 +121,7 @@ class Message extends StatelessWidget {
|
||||
),
|
||||
if (event.type == EventTypes.Encrypted &&
|
||||
event.messageType == MessageTypes.BadEncrypted &&
|
||||
event.content['body'] == DecryptError.UNKNOWN_SESSION)
|
||||
event.content['can_request_session'] == true)
|
||||
RaisedButton(
|
||||
color: color.withAlpha(100),
|
||||
child: Text(
|
||||
|
@ -14,6 +14,8 @@ import '../l10n/l10n.dart';
|
||||
import '../utils/beautify_string_extension.dart';
|
||||
import '../utils/famedlysdk_store.dart';
|
||||
import 'avatar.dart';
|
||||
import '../views/key_verification.dart';
|
||||
import '../utils/app_route.dart';
|
||||
|
||||
class Matrix extends StatefulWidget {
|
||||
static const String callNamespace = 'chat.fluffy.jitsi_call';
|
||||
@ -97,6 +99,7 @@ class MatrixState extends State<Matrix> {
|
||||
};
|
||||
|
||||
StreamSubscription onRoomKeyRequestSub;
|
||||
StreamSubscription onKeyVerificationRequestSub;
|
||||
StreamSubscription onJitsiCallSub;
|
||||
|
||||
void onJitsiCall(EventUpdate eventUpdate) {
|
||||
@ -159,7 +162,17 @@ class MatrixState extends State<Matrix> {
|
||||
store = widget.store ?? Store();
|
||||
if (widget.client == null) {
|
||||
debugPrint('[Matrix] Init matrix client');
|
||||
client = Client(widget.clientName, debug: false);
|
||||
final Set verificationMethods = <KeyVerificationMethod>{
|
||||
KeyVerificationMethod.numbers
|
||||
};
|
||||
if (!kIsWeb) {
|
||||
// emojis don't show in web somehow
|
||||
verificationMethods.add(KeyVerificationMethod.emoji);
|
||||
}
|
||||
client = Client(widget.clientName,
|
||||
debug: false,
|
||||
enableE2eeRecovery: true,
|
||||
verificationMethods: verificationMethods);
|
||||
onJitsiCallSub ??= client.onEvent.stream
|
||||
.where((e) =>
|
||||
e.type == 'timeline' &&
|
||||
@ -184,6 +197,23 @@ class MatrixState extends State<Matrix> {
|
||||
await request.forwardKey();
|
||||
}
|
||||
});
|
||||
onKeyVerificationRequestSub ??= client.onKeyVerificationRequest.stream
|
||||
.listen((KeyVerification request) async {
|
||||
if (await SimpleDialogs(context).askConfirmation(
|
||||
titleText: L10n.of(context).newVerificationRequest,
|
||||
contentText: L10n.of(context).askVerificationRequest(request.userId),
|
||||
)) {
|
||||
await request.acceptVerification();
|
||||
await Navigator.of(context).push(
|
||||
AppRoute.defaultRoute(
|
||||
context,
|
||||
KeyVerificationView(request: request),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await request.rejectVerification();
|
||||
}
|
||||
});
|
||||
_initWithStore();
|
||||
} else {
|
||||
client = widget.client;
|
||||
@ -210,6 +240,7 @@ class MatrixState extends State<Matrix> {
|
||||
@override
|
||||
void dispose() {
|
||||
onRoomKeyRequestSub?.cancel();
|
||||
onKeyVerificationRequestSub?.cancel();
|
||||
onJitsiCallSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
{
|
||||
"@@last_modified": "2020-05-15T15:34:50.065646",
|
||||
"@@last_modified": "2020-06-25T16:02:16.297192",
|
||||
"About": "About",
|
||||
"@About": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Accept": "Accept",
|
||||
"@Accept": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"acceptedTheInvitation": "{username} accepted the invitation",
|
||||
"@acceptedTheInvitation": {
|
||||
"type": "text",
|
||||
@ -74,6 +79,28 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"askSSSSCache": "Please enter your secure store passphrase or recovery key to cache the keys.",
|
||||
"@askSSSSCache": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"askSSSSSign": "To be able to sign the other person, please enter your secure store passphrase or recovery key.",
|
||||
"@askSSSSSign": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"askSSSSVerify": "Please enter your secure store passphrase or recovery key to verify your session.",
|
||||
"@askSSSSVerify": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"askVerificationRequest": "Accept this verification request from {username}?",
|
||||
"@askVerificationRequest": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"username": {}
|
||||
}
|
||||
},
|
||||
"Authentication": "Authentication",
|
||||
"@Authentication": {
|
||||
"type": "text",
|
||||
@ -102,6 +129,11 @@
|
||||
"targetName": {}
|
||||
}
|
||||
},
|
||||
"Block Device": "Block Device",
|
||||
"@Block Device": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"byDefaultYouWillBeConnectedTo": "By default you will be connected to {homeserver}",
|
||||
"@byDefaultYouWillBeConnectedTo": {
|
||||
"type": "text",
|
||||
@ -109,6 +141,11 @@
|
||||
"homeserver": {}
|
||||
}
|
||||
},
|
||||
"cachedKeys": "Successfully cached keys!",
|
||||
"@cachedKeys": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Cancel": "Cancel",
|
||||
"@Cancel": {
|
||||
"type": "text",
|
||||
@ -273,6 +310,16 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"compareEmojiMatch": "Compare and make sure the following emoji match those of the other device:",
|
||||
"@compareEmojiMatch": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"compareNumbersMatch": "Compare and make sure the following numbers match those of the other device:",
|
||||
"@compareNumbersMatch": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Confirm": "Confirm",
|
||||
"@Confirm": {
|
||||
"type": "text",
|
||||
@ -354,6 +401,16 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"crossSigningDisabled": "Cross-Signing is disabled",
|
||||
"@crossSigningDisabled": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"crossSigningEnabled": "Cross-Signing is enabled",
|
||||
"@crossSigningEnabled": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Currently active": "Currently active",
|
||||
"@Currently active": {
|
||||
"type": "text",
|
||||
@ -464,6 +521,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Encryption": "Encryption",
|
||||
"@Encryption": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Encryption algorithm": "Encryption algorithm",
|
||||
"@Encryption algorithm": {
|
||||
"type": "text",
|
||||
@ -594,6 +656,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"incorrectPassphraseOrKey": "Incorrect passphrase or recovery key",
|
||||
"@incorrectPassphraseOrKey": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Invite contact": "Invite contact",
|
||||
"@Invite contact": {
|
||||
"type": "text",
|
||||
@ -632,6 +699,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"isDeviceKeyCorrect": "Is the following device key correct?",
|
||||
"@isDeviceKeyCorrect": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"is typing...": "is typing...",
|
||||
"@is typing...": {
|
||||
"type": "text",
|
||||
@ -649,6 +721,16 @@
|
||||
"username": {}
|
||||
}
|
||||
},
|
||||
"keysCached": "Keys are cached",
|
||||
"@keysCached": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"keysMissing": "Keys are missing",
|
||||
"@keysMissing": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"kicked": "{username} kicked {targetName}",
|
||||
"@kicked": {
|
||||
"type": "text",
|
||||
@ -788,6 +870,21 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"newVerificationRequest": "New verification request!",
|
||||
"@newVerificationRequest": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"noCrossSignBootstrap": "Fluffychat currently does not support enabling Cross-Signing. Please enable it from within Riot.",
|
||||
"@noCrossSignBootstrap": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"noMegolmBootstrap": "Fluffychat currently does not support enabling Online Key Backup. Please enable it from within Riot.",
|
||||
"@noMegolmBootstrap": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"It seems that you have no google services on your phone. That's a good decision for your privacy! To receive push notifications in FluffyChat we recommend using microG: https://microg.org/": "It seems that you have no google services on your phone. That's a good decision for your privacy! To receive push notifications in FluffyChat we recommend using microG: https://microg.org/",
|
||||
"@It seems that you have no google services on your phone. That's a good decision for your privacy! To receive push notifications in FluffyChat we recommend using microG: https://microg.org/": {
|
||||
"type": "text",
|
||||
@ -830,6 +927,16 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"onlineKeyBackupDisabled": "Online Key Backup is disabled",
|
||||
"@onlineKeyBackupDisabled": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"onlineKeyBackupEnabled": "Online Key Backup is enabled",
|
||||
"@onlineKeyBackupEnabled": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Oops something went wrong...": "Oops something went wrong...",
|
||||
"@Oops something went wrong...": {
|
||||
"type": "text",
|
||||
@ -855,6 +962,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"passphraseOrKey": "passphrase or recovery key",
|
||||
"@passphraseOrKey": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Password": "Password",
|
||||
"@Password": {
|
||||
"type": "text",
|
||||
@ -897,6 +1009,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Reject": "Reject",
|
||||
"@Reject": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Rejoin": "Rejoin",
|
||||
"@Rejoin": {
|
||||
"type": "text",
|
||||
@ -978,6 +1095,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Room has been upgraded": "Room has been upgraded",
|
||||
"@Room has been upgraded": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Saturday": "Saturday",
|
||||
"@Saturday": {
|
||||
"type": "text",
|
||||
@ -1083,6 +1205,11 @@
|
||||
"username": {}
|
||||
}
|
||||
},
|
||||
"sessionVerified": "Session is verified",
|
||||
"@sessionVerified": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Set a profile picture": "Set a profile picture",
|
||||
"@Set a profile picture": {
|
||||
"type": "text",
|
||||
@ -1113,6 +1240,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Skip": "Skip",
|
||||
"@Skip": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Change your style": "Change your style",
|
||||
"@Change your style": {
|
||||
"type": "text",
|
||||
@ -1153,6 +1285,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Submit": "Submit",
|
||||
"@Submit": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Sunday": "Sunday",
|
||||
"@Sunday": {
|
||||
"type": "text",
|
||||
@ -1168,6 +1305,16 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"They Don't Match": "They Don't Match",
|
||||
"@They Don't Match": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"They Match": "They Match",
|
||||
"@They Match": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"This room has been archived.": "This room has been archived.",
|
||||
"@This room has been archived.": {
|
||||
"type": "text",
|
||||
@ -1212,6 +1359,11 @@
|
||||
"targetName": {}
|
||||
}
|
||||
},
|
||||
"Unblock Device": "Unblock Device",
|
||||
"@Unblock Device": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Unmute chat": "Unmute chat",
|
||||
"@Unmute chat": {
|
||||
"type": "text",
|
||||
@ -1227,6 +1379,11 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"unknownSessionVerify": "Unknown session, please verify",
|
||||
"@unknownSessionVerify": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"unknownEvent": "Unknown event '{type}'",
|
||||
"@unknownEvent": {
|
||||
"type": "text",
|
||||
@ -1297,6 +1454,36 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"verifyManual": "Verify Manually",
|
||||
"@verifyManual": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"verifiedSession": "Successfully verified session!",
|
||||
"@verifiedSession": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"verifyStart": "Start Verification",
|
||||
"@verifyStart": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"verifySuccess": "You successfully verified!",
|
||||
"@verifySuccess": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"verifyTitle": "Verifying other account",
|
||||
"@verifyTitle": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Verify User": "Verify User",
|
||||
"@Verify User": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Video call": "Video call",
|
||||
"@Video call": {
|
||||
"type": "text",
|
||||
@ -1322,6 +1509,21 @@
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"waitingPartnerAcceptRequest": "Waiting for partner to accept the request...",
|
||||
"@waitingPartnerAcceptRequest": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"waitingPartnerEmoji": "Waiting for partner to accept the emoji...",
|
||||
"@waitingPartnerEmoji": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"waitingPartnerNumbers": "Waiting for partner to accept the numbers...",
|
||||
"@waitingPartnerNumbers": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"Wallpaper": "Wallpaper",
|
||||
"@Wallpaper": {
|
||||
"type": "text",
|
||||
|
@ -46,6 +46,8 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get about => Intl.message("About");
|
||||
|
||||
String get accept => Intl.message("Accept");
|
||||
|
||||
String acceptedTheInvitation(String username) => Intl.message(
|
||||
"$username accepted the invitation",
|
||||
name: "acceptedTheInvitation",
|
||||
@ -81,6 +83,22 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get areYouSure => Intl.message("Are you sure?");
|
||||
|
||||
String get askSSSSCache => Intl.message(
|
||||
"Please enter your secure store passphrase or recovery key to cache the keys.",
|
||||
name: "askSSSSCache");
|
||||
|
||||
String get askSSSSSign => Intl.message(
|
||||
"To be able to sign the other person, please enter your secure store passphrase or recovery key.",
|
||||
name: "askSSSSSign");
|
||||
|
||||
String get askSSSSVerify => Intl.message(
|
||||
"Please enter your secure store passphrase or recovery key to verify your session.",
|
||||
name: "askSSSSVerify");
|
||||
|
||||
String askVerificationRequest(String username) =>
|
||||
Intl.message("Accept this verification request from $username?",
|
||||
name: "askVerificationRequest", args: [username]);
|
||||
|
||||
String get authentication => Intl.message("Authentication");
|
||||
|
||||
String get avatarHasBeenChanged => Intl.message("Avatar has been changed");
|
||||
@ -95,12 +113,17 @@ class L10n extends MatrixLocalizations {
|
||||
args: [username, targetName],
|
||||
);
|
||||
|
||||
String get blockDevice => Intl.message("Block Device");
|
||||
|
||||
String byDefaultYouWillBeConnectedTo(String homeserver) => Intl.message(
|
||||
'By default you will be connected to $homeserver',
|
||||
name: 'byDefaultYouWillBeConnectedTo',
|
||||
args: [homeserver],
|
||||
);
|
||||
|
||||
String get cachedKeys =>
|
||||
Intl.message("Successfully cached keys!", name: "cachedKeys");
|
||||
|
||||
String get cancel => Intl.message("Cancel");
|
||||
|
||||
String changedTheChatAvatar(String username) => Intl.message(
|
||||
@ -216,6 +239,14 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get close => Intl.message("Close");
|
||||
|
||||
String get compareEmojiMatch => Intl.message(
|
||||
"Compare and make sure the following emoji match those of the other device:",
|
||||
name: "compareEmojiMatch");
|
||||
|
||||
String get compareNumbersMatch => Intl.message(
|
||||
"Compare and make sure the following numbers match those of the other device:",
|
||||
name: "compareNumbersMatch");
|
||||
|
||||
String get confirm => Intl.message("Confirm");
|
||||
|
||||
String get connect => Intl.message('Connect');
|
||||
@ -261,6 +292,12 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get createNewGroup => Intl.message("Create new group");
|
||||
|
||||
String get crossSigningDisabled =>
|
||||
Intl.message("Cross-Signing is disabled", name: "crossSigningDisabled");
|
||||
|
||||
String get crossSigningEnabled =>
|
||||
Intl.message("Cross-Signing is enabled", name: "crossSigningEnabled");
|
||||
|
||||
String get currentlyActive => Intl.message('Currently active');
|
||||
|
||||
String dateAndTimeOfDay(String date, String timeOfDay) => Intl.message(
|
||||
@ -319,6 +356,8 @@ class L10n extends MatrixLocalizations {
|
||||
String get enableEncryptionWarning => Intl.message(
|
||||
"You won't be able to disable the encryption anymore. Are you sure?");
|
||||
|
||||
String get encryption => Intl.message("Encryption");
|
||||
|
||||
String get encryptionAlgorithm => Intl.message("Encryption algorithm");
|
||||
|
||||
String get encryptionNotEnabled => Intl.message("Encryption is not enabled");
|
||||
@ -381,6 +420,10 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get identity => Intl.message("Identity");
|
||||
|
||||
String get incorrectPassphraseOrKey =>
|
||||
Intl.message("Incorrect passphrase or recovery key",
|
||||
name: "incorrectPassphraseOrKey");
|
||||
|
||||
String get inviteContact => Intl.message("Invite contact");
|
||||
|
||||
String inviteContactToGroup(String groupName) => Intl.message(
|
||||
@ -405,6 +448,10 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get invitedUsersOnly => Intl.message("Invited users only");
|
||||
|
||||
String get isDeviceKeyCorrect =>
|
||||
Intl.message("Is the following device key correct?",
|
||||
name: "isDeviceKeyCorrect");
|
||||
|
||||
String get isTyping => Intl.message("is typing...");
|
||||
|
||||
String get editJitsiInstance => Intl.message('Edit Jitsi instance');
|
||||
@ -415,6 +462,11 @@ class L10n extends MatrixLocalizations {
|
||||
args: [username],
|
||||
);
|
||||
|
||||
String get keysCached => Intl.message("Keys are cached", name: "keysCached");
|
||||
|
||||
String get keysMissing =>
|
||||
Intl.message("Keys are missing", name: "keysMissing");
|
||||
|
||||
String kicked(String username, String targetName) => Intl.message(
|
||||
"$username kicked $targetName",
|
||||
name: "kicked",
|
||||
@ -493,6 +545,17 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get newPrivateChat => Intl.message("New private chat");
|
||||
|
||||
String get newVerificationRequest =>
|
||||
Intl.message("New verification request!", name: "newVerificationRequest");
|
||||
|
||||
String get noCrossSignBootstrap => Intl.message(
|
||||
"Fluffychat currently does not support enabling Cross-Signing. Please enable it from within Riot.",
|
||||
name: "noCrossSignBootstrap");
|
||||
|
||||
String get noMegolmBootstrap => Intl.message(
|
||||
"Fluffychat currently does not support enabling Online Key Backup. Please enable it from within Riot.",
|
||||
name: "noMegolmBootstrap");
|
||||
|
||||
String get noGoogleServicesWarning => Intl.message(
|
||||
"It seems that you have no google services on your phone. That's a good decision for your privacy! To receive push notifications in FluffyChat we recommend using microG: https://microg.org/");
|
||||
|
||||
@ -511,6 +574,14 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get ok => Intl.message('ok');
|
||||
|
||||
String get onlineKeyBackupDisabled =>
|
||||
Intl.message("Online Key Backup is disabled",
|
||||
name: "onlineKeyBackupDisabled");
|
||||
|
||||
String get onlineKeyBackupEnabled =>
|
||||
Intl.message("Online Key Backup is enabled",
|
||||
name: "onlineKeyBackupEnabled");
|
||||
|
||||
String get oopsSomethingWentWrong =>
|
||||
Intl.message("Oops something went wrong...");
|
||||
|
||||
@ -523,6 +594,9 @@ class L10n extends MatrixLocalizations {
|
||||
String get participatingUserDevices =>
|
||||
Intl.message("Participating user devices");
|
||||
|
||||
String get passphraseOrKey =>
|
||||
Intl.message("passphrase or recovery key", name: "passphraseOrKey");
|
||||
|
||||
String get password => Intl.message("Password");
|
||||
|
||||
String get pickImage => Intl.message('Pick image');
|
||||
@ -546,6 +620,8 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get publicRooms => Intl.message("Public Rooms");
|
||||
|
||||
String get reject => Intl.message("Reject");
|
||||
|
||||
String get rejoin => Intl.message("Rejoin");
|
||||
|
||||
String get renderRichContent => Intl.message("Render rich message content");
|
||||
@ -662,6 +738,9 @@ class L10n extends MatrixLocalizations {
|
||||
args: [username],
|
||||
);
|
||||
|
||||
String get sessionVerified =>
|
||||
Intl.message("Session is verified", name: "sessionVerified");
|
||||
|
||||
String get setAProfilePicture => Intl.message("Set a profile picture");
|
||||
|
||||
String get setGroupDescription => Intl.message("Set group description");
|
||||
@ -674,6 +753,8 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get signUp => Intl.message("Sign up");
|
||||
|
||||
String get skip => Intl.message("Skip");
|
||||
|
||||
String get changeTheme => Intl.message("Change your style");
|
||||
|
||||
String get systemTheme => Intl.message("System");
|
||||
@ -690,12 +771,18 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get startYourFirstChat => Intl.message("Start your first chat :-)");
|
||||
|
||||
String get submit => Intl.message("Submit");
|
||||
|
||||
String get sunday => Intl.message("Sunday");
|
||||
|
||||
String get donate => Intl.message("Donate");
|
||||
|
||||
String get tapToShowMenu => Intl.message("Tap to show menu");
|
||||
|
||||
String get theyDontMatch => Intl.message("They Don't Match");
|
||||
|
||||
String get theyMatch => Intl.message("They Match");
|
||||
|
||||
String get thisRoomHasBeenArchived =>
|
||||
Intl.message("This room has been archived.");
|
||||
|
||||
@ -726,6 +813,8 @@ class L10n extends MatrixLocalizations {
|
||||
args: [username, targetName],
|
||||
);
|
||||
|
||||
String get unblockDevice => Intl.message("Unblock Device");
|
||||
|
||||
String get unmuteChat => Intl.message('Unmute chat');
|
||||
|
||||
String get unknownDevice => Intl.message("Unknown device");
|
||||
@ -733,6 +822,10 @@ class L10n extends MatrixLocalizations {
|
||||
String get unknownEncryptionAlgorithm =>
|
||||
Intl.message("Unknown encryption algorithm");
|
||||
|
||||
String get unknownSessionVerify =>
|
||||
Intl.message("Unknown session, please verify",
|
||||
name: "unknownSessionVerify");
|
||||
|
||||
String unknownEvent(String type) => Intl.message(
|
||||
"Unknown event '$type'",
|
||||
name: "unknownEvent",
|
||||
@ -787,6 +880,23 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get verify => Intl.message("Verify");
|
||||
|
||||
String get verifyManual =>
|
||||
Intl.message("Verify Manually", name: "verifyManual");
|
||||
|
||||
String get verifiedSession =>
|
||||
Intl.message("Successfully verified session!", name: "verifiedSession");
|
||||
|
||||
String get verifyStart =>
|
||||
Intl.message("Start Verification", name: "verifyStart");
|
||||
|
||||
String get verifySuccess =>
|
||||
Intl.message("You successfully verified!", name: "verifySuccess");
|
||||
|
||||
String get verifyTitle =>
|
||||
Intl.message("Verifying other account", name: "verifyTitle");
|
||||
|
||||
String get verifyUser => Intl.message("Verify User");
|
||||
|
||||
String get videoCall => Intl.message('Video call');
|
||||
|
||||
String get visibleForAllParticipants =>
|
||||
@ -799,6 +909,18 @@ class L10n extends MatrixLocalizations {
|
||||
|
||||
String get voiceMessage => Intl.message("Voice message");
|
||||
|
||||
String get waitingPartnerAcceptRequest =>
|
||||
Intl.message("Waiting for partner to accept the request...",
|
||||
name: "waitingPartnerAcceptRequest");
|
||||
|
||||
String get waitingPartnerEmoji =>
|
||||
Intl.message("Waiting for partner to accept the emoji...",
|
||||
name: "waitingPartnerEmoji");
|
||||
|
||||
String get waitingPartnerNumbers =>
|
||||
Intl.message("Waiting for partner to accept the numbers...",
|
||||
name: "waitingPartnerNumbers");
|
||||
|
||||
String get wallpaper => Intl.message("Wallpaper");
|
||||
|
||||
String get warningEncryptionInBeta => Intl.message(
|
||||
|
@ -23,6 +23,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
static m1(username) => "${username} activated end to end encryption";
|
||||
|
||||
static m60(username) => "Accept this verification request from ${username}?";
|
||||
|
||||
static m2(username, targetName) => "${username} banned ${targetName}";
|
||||
|
||||
static m3(homeserver) => "By default you will be connected to ${homeserver}";
|
||||
@ -157,6 +159,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"(Optional) Group name":
|
||||
MessageLookupByLibrary.simpleMessage("(Optional) Group name"),
|
||||
"About": MessageLookupByLibrary.simpleMessage("About"),
|
||||
"Accept": MessageLookupByLibrary.simpleMessage("Accept"),
|
||||
"Account": MessageLookupByLibrary.simpleMessage("Account"),
|
||||
"Account informations":
|
||||
MessageLookupByLibrary.simpleMessage("Account informations"),
|
||||
@ -178,6 +181,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Avatar has been changed"),
|
||||
"Ban from chat": MessageLookupByLibrary.simpleMessage("Ban from chat"),
|
||||
"Banned": MessageLookupByLibrary.simpleMessage("Banned"),
|
||||
"Block Device": MessageLookupByLibrary.simpleMessage("Block Device"),
|
||||
"Cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
|
||||
"Change the homeserver":
|
||||
MessageLookupByLibrary.simpleMessage("Change the homeserver"),
|
||||
@ -242,6 +246,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Emote shortcode":
|
||||
MessageLookupByLibrary.simpleMessage("Emote shortcode"),
|
||||
"Empty chat": MessageLookupByLibrary.simpleMessage("Empty chat"),
|
||||
"Encryption": MessageLookupByLibrary.simpleMessage("Encryption"),
|
||||
"Encryption algorithm":
|
||||
MessageLookupByLibrary.simpleMessage("Encryption algorithm"),
|
||||
"Encryption is not enabled":
|
||||
@ -351,6 +356,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Please enter your username"),
|
||||
"Public Rooms": MessageLookupByLibrary.simpleMessage("Public Rooms"),
|
||||
"Recording": MessageLookupByLibrary.simpleMessage("Recording"),
|
||||
"Reject": MessageLookupByLibrary.simpleMessage("Reject"),
|
||||
"Rejoin": MessageLookupByLibrary.simpleMessage("Rejoin"),
|
||||
"Remove": MessageLookupByLibrary.simpleMessage("Remove"),
|
||||
"Remove all other devices":
|
||||
@ -368,6 +374,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Request to read older messages"),
|
||||
"Revoke all permissions":
|
||||
MessageLookupByLibrary.simpleMessage("Revoke all permissions"),
|
||||
"Room has been upgraded":
|
||||
MessageLookupByLibrary.simpleMessage("Room has been upgraded"),
|
||||
"Saturday": MessageLookupByLibrary.simpleMessage("Saturday"),
|
||||
"Search for a chat":
|
||||
MessageLookupByLibrary.simpleMessage("Search for a chat"),
|
||||
@ -388,9 +396,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Settings": MessageLookupByLibrary.simpleMessage("Settings"),
|
||||
"Share": MessageLookupByLibrary.simpleMessage("Share"),
|
||||
"Sign up": MessageLookupByLibrary.simpleMessage("Sign up"),
|
||||
"Skip": MessageLookupByLibrary.simpleMessage("Skip"),
|
||||
"Source code": MessageLookupByLibrary.simpleMessage("Source code"),
|
||||
"Start your first chat :-)":
|
||||
MessageLookupByLibrary.simpleMessage("Start your first chat :-)"),
|
||||
"Submit": MessageLookupByLibrary.simpleMessage("Submit"),
|
||||
"Sunday": MessageLookupByLibrary.simpleMessage("Sunday"),
|
||||
"System": MessageLookupByLibrary.simpleMessage("System"),
|
||||
"Tap to show menu":
|
||||
@ -398,12 +408,17 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"The encryption has been corrupted":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The encryption has been corrupted"),
|
||||
"They Don\'t Match":
|
||||
MessageLookupByLibrary.simpleMessage("They Don\'t Match"),
|
||||
"They Match": MessageLookupByLibrary.simpleMessage("They Match"),
|
||||
"This room has been archived.": MessageLookupByLibrary.simpleMessage(
|
||||
"This room has been archived."),
|
||||
"Thursday": MessageLookupByLibrary.simpleMessage("Thursday"),
|
||||
"Try to send again":
|
||||
MessageLookupByLibrary.simpleMessage("Try to send again"),
|
||||
"Tuesday": MessageLookupByLibrary.simpleMessage("Tuesday"),
|
||||
"Unblock Device":
|
||||
MessageLookupByLibrary.simpleMessage("Unblock Device"),
|
||||
"Unknown device":
|
||||
MessageLookupByLibrary.simpleMessage("Unknown device"),
|
||||
"Unknown encryption algorithm": MessageLookupByLibrary.simpleMessage(
|
||||
@ -413,6 +428,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Use Amoled compatible colors?"),
|
||||
"Username": MessageLookupByLibrary.simpleMessage("Username"),
|
||||
"Verify": MessageLookupByLibrary.simpleMessage("Verify"),
|
||||
"Verify User": MessageLookupByLibrary.simpleMessage("Verify User"),
|
||||
"Video call": MessageLookupByLibrary.simpleMessage("Video call"),
|
||||
"Visibility of the chat history": MessageLookupByLibrary.simpleMessage(
|
||||
"Visibility of the chat history"),
|
||||
@ -451,8 +467,17 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"acceptedTheInvitation": m0,
|
||||
"activatedEndToEndEncryption": m1,
|
||||
"alias": MessageLookupByLibrary.simpleMessage("alias"),
|
||||
"askSSSSCache": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter your secure store passphrase or recovery key to cache the keys."),
|
||||
"askSSSSSign": MessageLookupByLibrary.simpleMessage(
|
||||
"To be able to sign the other person, please enter your secure store passphrase or recovery key."),
|
||||
"askSSSSVerify": MessageLookupByLibrary.simpleMessage(
|
||||
"Please enter your secure store passphrase or recovery key to verify your session."),
|
||||
"askVerificationRequest": m60,
|
||||
"bannedUser": m2,
|
||||
"byDefaultYouWillBeConnectedTo": m3,
|
||||
"cachedKeys":
|
||||
MessageLookupByLibrary.simpleMessage("Successfully cached keys!"),
|
||||
"changedTheChatAvatar": m4,
|
||||
"changedTheChatDescriptionTo": m5,
|
||||
"changedTheChatNameTo": m6,
|
||||
@ -467,9 +492,17 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"changedTheProfileAvatar": m15,
|
||||
"changedTheRoomAliases": m16,
|
||||
"changedTheRoomInvitationLink": m17,
|
||||
"compareEmojiMatch": MessageLookupByLibrary.simpleMessage(
|
||||
"Compare and make sure the following emoji match those of the other device:"),
|
||||
"compareNumbersMatch": MessageLookupByLibrary.simpleMessage(
|
||||
"Compare and make sure the following numbers match those of the other device:"),
|
||||
"couldNotDecryptMessage": m18,
|
||||
"countParticipants": m19,
|
||||
"createdTheChat": m20,
|
||||
"crossSigningDisabled":
|
||||
MessageLookupByLibrary.simpleMessage("Cross-Signing is disabled"),
|
||||
"crossSigningEnabled":
|
||||
MessageLookupByLibrary.simpleMessage("Cross-Signing is enabled"),
|
||||
"dateAndTimeOfDay": m21,
|
||||
"dateWithYear": m22,
|
||||
"dateWithoutYear": m23,
|
||||
@ -481,18 +514,36 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"You need to pick an emote shortcode and an image!"),
|
||||
"groupWith": m24,
|
||||
"hasWithdrawnTheInvitationFor": m25,
|
||||
"incorrectPassphraseOrKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Incorrect passphrase or recovery key"),
|
||||
"inviteContactToGroup": m26,
|
||||
"inviteText": m27,
|
||||
"invitedUser": m28,
|
||||
"is typing...": MessageLookupByLibrary.simpleMessage("is typing..."),
|
||||
"isDeviceKeyCorrect": MessageLookupByLibrary.simpleMessage(
|
||||
"Is the following device key correct?"),
|
||||
"joinedTheChat": m29,
|
||||
"keysCached": MessageLookupByLibrary.simpleMessage("Keys are cached"),
|
||||
"keysMissing": MessageLookupByLibrary.simpleMessage("Keys are missing"),
|
||||
"kicked": m30,
|
||||
"kickedAndBanned": m31,
|
||||
"lastActiveAgo": m32,
|
||||
"loadCountMoreParticipants": m33,
|
||||
"logInTo": m34,
|
||||
"newVerificationRequest":
|
||||
MessageLookupByLibrary.simpleMessage("New verification request!"),
|
||||
"noCrossSignBootstrap": MessageLookupByLibrary.simpleMessage(
|
||||
"Fluffychat currently does not support enabling Cross-Signing. Please enable it from within Riot."),
|
||||
"noMegolmBootstrap": MessageLookupByLibrary.simpleMessage(
|
||||
"Fluffychat currently does not support enabling Online Key Backup. Please enable it from within Riot."),
|
||||
"numberSelected": m35,
|
||||
"ok": MessageLookupByLibrary.simpleMessage("ok"),
|
||||
"onlineKeyBackupDisabled": MessageLookupByLibrary.simpleMessage(
|
||||
"Online Key Backup is disabled"),
|
||||
"onlineKeyBackupEnabled": MessageLookupByLibrary.simpleMessage(
|
||||
"Online Key Backup is enabled"),
|
||||
"passphraseOrKey":
|
||||
MessageLookupByLibrary.simpleMessage("passphrase or recovery key"),
|
||||
"play": m36,
|
||||
"redactedAnEvent": m37,
|
||||
"rejectedTheInvitation": m38,
|
||||
@ -505,11 +556,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"sentASticker": m45,
|
||||
"sentAVideo": m46,
|
||||
"sentAnAudio": m47,
|
||||
"sessionVerified":
|
||||
MessageLookupByLibrary.simpleMessage("Session is verified"),
|
||||
"sharedTheLocation": m48,
|
||||
"timeOfDay": m49,
|
||||
"title": MessageLookupByLibrary.simpleMessage("FluffyChat"),
|
||||
"unbannedUser": m50,
|
||||
"unknownEvent": m51,
|
||||
"unknownSessionVerify": MessageLookupByLibrary.simpleMessage(
|
||||
"Unknown session, please verify"),
|
||||
"unreadChats": m52,
|
||||
"unreadMessages": m53,
|
||||
"unreadMessagesInChats": m54,
|
||||
@ -517,6 +572,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"userAndUserAreTyping": m56,
|
||||
"userIsTyping": m57,
|
||||
"userLeftTheChat": m58,
|
||||
"userSentUnknownEvent": m59
|
||||
"userSentUnknownEvent": m59,
|
||||
"verifiedSession": MessageLookupByLibrary.simpleMessage(
|
||||
"Successfully verified session!"),
|
||||
"verifyManual": MessageLookupByLibrary.simpleMessage("Verify Manually"),
|
||||
"verifyStart":
|
||||
MessageLookupByLibrary.simpleMessage("Start Verification"),
|
||||
"verifySuccess":
|
||||
MessageLookupByLibrary.simpleMessage("You successfully verified!"),
|
||||
"verifyTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Verifying other account"),
|
||||
"waitingPartnerAcceptRequest": MessageLookupByLibrary.simpleMessage(
|
||||
"Waiting for partner to accept the request..."),
|
||||
"waitingPartnerEmoji": MessageLookupByLibrary.simpleMessage(
|
||||
"Waiting for partner to accept the emoji..."),
|
||||
"waitingPartnerNumbers": MessageLookupByLibrary.simpleMessage(
|
||||
"Waiting for partner to accept the numbers...")
|
||||
};
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ Future<void> migrate(String clientName, Database db, Store store) async {
|
||||
var sess = olm.Session();
|
||||
sess.unpickle(credentials['userID'], pickle);
|
||||
await db.storeOlmSession(
|
||||
clientId, identKey, sess.session_id(), pickle);
|
||||
clientId, identKey, sess.session_id(), pickle, null);
|
||||
sess?.free();
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,13 @@ extension RoomStatusExtension on Room {
|
||||
if (directChatPresence.presence.currentlyActive == true) {
|
||||
return L10n.of(context).currentlyActive;
|
||||
}
|
||||
return L10n.of(context).lastActiveAgo(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
directChatPresence.presence.lastActiveAgo)
|
||||
.localizedTimeShort(context));
|
||||
if (directChatPresence.presence.lastActiveAgo == null) {
|
||||
return L10n.of(context).lastSeenLongTimeAgo;
|
||||
}
|
||||
final time = DateTime.fromMillisecondsSinceEpoch(
|
||||
DateTime.now().millisecondsSinceEpoch -
|
||||
directChatPresence.presence.lastActiveAgo);
|
||||
return L10n.of(context).lastActiveAgo(time.localizedTimeShort(context));
|
||||
}
|
||||
return L10n.of(context).lastSeenLongTimeAgo;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:famedlysdk/encryption.dart';
|
||||
import 'package:fluffychat/components/adaptive_page_layout.dart';
|
||||
import 'package:fluffychat/components/avatar.dart';
|
||||
import 'package:fluffychat/components/matrix.dart';
|
||||
@ -6,6 +7,9 @@ import 'package:fluffychat/utils/beautify_string_extension.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/views/chat_list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'key_verification.dart';
|
||||
import '../utils/app_route.dart';
|
||||
import '../components/dialogs/simple_dialogs.dart';
|
||||
|
||||
class ChatEncryptionSettingsView extends StatelessWidget {
|
||||
final String id;
|
||||
@ -33,6 +37,70 @@ class ChatEncryptionSettings extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
|
||||
Future<void> onSelected(
|
||||
BuildContext context, String action, DeviceKeys key) async {
|
||||
final room = Matrix.of(context).client.getRoomById(widget.id);
|
||||
final unblock = () async {
|
||||
if (key.blocked) {
|
||||
await key.setBlocked(false);
|
||||
}
|
||||
};
|
||||
switch (action) {
|
||||
case 'verify':
|
||||
await unblock();
|
||||
final req = key.startVerification();
|
||||
req.onUpdate = () {
|
||||
if (req.state == KeyVerificationState.done) {
|
||||
setState(() => null);
|
||||
}
|
||||
};
|
||||
await Navigator.of(context).push(
|
||||
AppRoute.defaultRoute(
|
||||
context,
|
||||
KeyVerificationView(request: req),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case 'verify_manual':
|
||||
if (await SimpleDialogs(context).askConfirmation(
|
||||
titleText: L10n.of(context).isDeviceKeyCorrect,
|
||||
contentText: key.ed25519Key.beautified,
|
||||
)) {
|
||||
await unblock();
|
||||
await key.setVerified(true);
|
||||
setState(() => null);
|
||||
}
|
||||
break;
|
||||
case 'verify_user':
|
||||
await unblock();
|
||||
final req =
|
||||
await room.client.userDeviceKeys[key.userId].startVerification();
|
||||
req.onUpdate = () {
|
||||
if (req.state == KeyVerificationState.done) {
|
||||
setState(() => null);
|
||||
}
|
||||
};
|
||||
await Navigator.of(context).push(
|
||||
AppRoute.defaultRoute(
|
||||
context,
|
||||
KeyVerificationView(request: req),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case 'block':
|
||||
if (key.directVerified) {
|
||||
await key.setVerified(false);
|
||||
}
|
||||
await key.setBlocked(true);
|
||||
setState(() => null);
|
||||
break;
|
||||
case 'unblock':
|
||||
await unblock();
|
||||
setState(() => null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final room = Matrix.of(context).client.getRoomById(widget.id);
|
||||
@ -68,6 +136,24 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
|
||||
if (i == 0 ||
|
||||
deviceKeys[i].userId != deviceKeys[i - 1].userId)
|
||||
Material(
|
||||
child: PopupMenuButton(
|
||||
onSelected: (action) =>
|
||||
onSelected(context, action, deviceKeys[i]),
|
||||
itemBuilder: (c) {
|
||||
var items = <PopupMenuEntry<String>>[];
|
||||
if (room
|
||||
.client
|
||||
.userDeviceKeys[deviceKeys[i].userId]
|
||||
.verified ==
|
||||
UserVerifiedStatus.unknown &&
|
||||
deviceKeys[i].userId != room.client.userID) {
|
||||
items.add(PopupMenuItem(
|
||||
child: Text(L10n.of(context).verifyUser),
|
||||
value: 'verify_user',
|
||||
));
|
||||
}
|
||||
return items;
|
||||
},
|
||||
child: ListTile(
|
||||
leading: Avatar(
|
||||
room
|
||||
@ -82,9 +168,47 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
|
||||
.calcDisplayname()),
|
||||
subtitle: Text(deviceKeys[i].userId),
|
||||
),
|
||||
),
|
||||
elevation: 2,
|
||||
),
|
||||
CheckboxListTile(
|
||||
PopupMenuButton(
|
||||
onSelected: (action) =>
|
||||
onSelected(context, action, deviceKeys[i]),
|
||||
itemBuilder: (c) {
|
||||
var items = <PopupMenuEntry<String>>[];
|
||||
if (deviceKeys[i].blocked ||
|
||||
!deviceKeys[i].verified) {
|
||||
if (deviceKeys[i].userId == room.client.userID) {
|
||||
items.add(PopupMenuItem(
|
||||
child: Text(L10n.of(context).verifyStart),
|
||||
value: 'verify',
|
||||
));
|
||||
items.add(PopupMenuItem(
|
||||
child: Text(L10n.of(context).verifyManual),
|
||||
value: 'verify_manual',
|
||||
));
|
||||
} else {
|
||||
items.add(PopupMenuItem(
|
||||
child: Text(L10n.of(context).verifyUser),
|
||||
value: 'verify_user',
|
||||
));
|
||||
}
|
||||
}
|
||||
if (deviceKeys[i].blocked) {
|
||||
items.add(PopupMenuItem(
|
||||
child: Text(L10n.of(context).unblockDevice),
|
||||
value: 'unblock',
|
||||
));
|
||||
}
|
||||
if (!deviceKeys[i].blocked) {
|
||||
items.add(PopupMenuItem(
|
||||
child: Text(L10n.of(context).blockDevice),
|
||||
value: 'block',
|
||||
));
|
||||
}
|
||||
return items;
|
||||
},
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
"${deviceKeys[i].unsigned["device_display_name"] ?? L10n.of(context).unknownDevice} - ${deviceKeys[i].deviceId}",
|
||||
style: TextStyle(
|
||||
@ -99,28 +223,12 @@ class _ChatEncryptionSettingsState extends State<ChatEncryptionSettings> {
|
||||
.keys['ed25519:${deviceKeys[i].deviceId}']
|
||||
.beautified,
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).textTheme.bodyText2.color),
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText2
|
||||
.color),
|
||||
),
|
||||
),
|
||||
value: deviceKeys[i].verified,
|
||||
onChanged: (bool newVal) {
|
||||
if (newVal == true) {
|
||||
if (deviceKeys[i].blocked) {
|
||||
deviceKeys[i]
|
||||
.setBlocked(false, Matrix.of(context).client);
|
||||
}
|
||||
deviceKeys[i]
|
||||
.setVerified(true, Matrix.of(context).client);
|
||||
} else {
|
||||
if (deviceKeys[i].verified) {
|
||||
deviceKeys[i].setVerified(
|
||||
false, Matrix.of(context).client);
|
||||
}
|
||||
deviceKeys[i]
|
||||
.setBlocked(true, Matrix.of(context).client);
|
||||
}
|
||||
setState(() => null);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
347
lib/views/key_verification.dart
Normal file
347
lib/views/key_verification.dart
Normal file
@ -0,0 +1,347 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:famedlysdk/encryption.dart';
|
||||
import 'package:famedlysdk/matrix_api.dart';
|
||||
import 'chat_list.dart';
|
||||
import '../components/adaptive_page_layout.dart';
|
||||
import '../components/avatar.dart';
|
||||
import '../components/dialogs/simple_dialogs.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
|
||||
class KeyVerificationView extends StatelessWidget {
|
||||
final KeyVerification request;
|
||||
|
||||
KeyVerificationView({this.request});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AdaptivePageLayout(
|
||||
primaryPage: FocusPage.SECOND,
|
||||
firstScaffold: ChatList(),
|
||||
secondScaffold: KeyVerificationPage(request: request),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class KeyVerificationPage extends StatefulWidget {
|
||||
final KeyVerification request;
|
||||
|
||||
KeyVerificationPage({this.request});
|
||||
|
||||
@override
|
||||
_KeyVerificationPageState createState() => _KeyVerificationPageState();
|
||||
}
|
||||
|
||||
class _KeyVerificationPageState extends State<KeyVerificationPage> {
|
||||
void Function() originalOnUpdate;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
originalOnUpdate = widget.request.onUpdate;
|
||||
widget.request.onUpdate = () {
|
||||
if (originalOnUpdate != null) {
|
||||
originalOnUpdate();
|
||||
}
|
||||
setState(() => null);
|
||||
};
|
||||
widget.request.client.getProfileFromUserId(widget.request.userId).then((p) {
|
||||
profile = p;
|
||||
setState(() => null);
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.request.onUpdate =
|
||||
originalOnUpdate; // don't want to get updates anymore
|
||||
if (![KeyVerificationState.error, KeyVerificationState.done]
|
||||
.contains(widget.request.state)) {
|
||||
widget.request.cancel('m.user');
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Profile profile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget body;
|
||||
final buttons = <Widget>[];
|
||||
switch (widget.request.state) {
|
||||
case KeyVerificationState.askSSSS:
|
||||
// prompt the user for their ssss passphrase / key
|
||||
final textEditingController = TextEditingController();
|
||||
String input;
|
||||
final checkInput = () async {
|
||||
if (input == null) {
|
||||
return;
|
||||
}
|
||||
SimpleDialogs(context).showLoadingDialog(context);
|
||||
// make sure the loading spinner shows before we test the keys
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
var valid = false;
|
||||
try {
|
||||
await widget.request.openSSSS(recoveryKey: input);
|
||||
valid = true;
|
||||
} catch (_) {
|
||||
try {
|
||||
await widget.request.openSSSS(passphrase: input);
|
||||
valid = true;
|
||||
} catch (_) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
await Navigator.of(context)?.pop();
|
||||
if (!valid) {
|
||||
await SimpleDialogs(context).inform(
|
||||
contentText: L10n.of(context).incorrectPassphraseOrKey,
|
||||
);
|
||||
}
|
||||
};
|
||||
body = Container(
|
||||
margin: EdgeInsets.only(left: 8.0, right: 8.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Text(L10n.of(context).askSSSSSign,
|
||||
style: TextStyle(fontSize: 20)),
|
||||
Container(height: 10),
|
||||
TextField(
|
||||
controller: textEditingController,
|
||||
autofocus: false,
|
||||
autocorrect: false,
|
||||
onSubmitted: (s) {
|
||||
input = s;
|
||||
checkInput();
|
||||
},
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).passphraseOrKey,
|
||||
prefixStyle: TextStyle(color: Theme.of(context).primaryColor),
|
||||
suffixStyle: TextStyle(color: Theme.of(context).primaryColor),
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
),
|
||||
);
|
||||
buttons.add(RaisedButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
textColor: Colors.white,
|
||||
child: Text(L10n.of(context).submit),
|
||||
onPressed: () {
|
||||
input = textEditingController.text;
|
||||
checkInput();
|
||||
},
|
||||
));
|
||||
buttons.add(RaisedButton(
|
||||
textColor: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
color: Colors.white,
|
||||
child: Text(L10n.of(context).skip),
|
||||
onPressed: () => widget.request.openSSSS(skip: true),
|
||||
));
|
||||
break;
|
||||
case KeyVerificationState.askAccept:
|
||||
body = Container(
|
||||
child: Text(
|
||||
L10n.of(context).askVerificationRequest(widget.request.userId),
|
||||
style: TextStyle(fontSize: 20)),
|
||||
margin: EdgeInsets.only(left: 8.0, right: 8.0),
|
||||
);
|
||||
buttons.add(RaisedButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
textColor: Colors.white,
|
||||
child: Text(L10n.of(context).accept),
|
||||
onPressed: () => widget.request.acceptVerification(),
|
||||
));
|
||||
buttons.add(RaisedButton(
|
||||
textColor: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
color: Colors.white,
|
||||
child: Text(L10n.of(context).reject),
|
||||
onPressed: () {
|
||||
widget.request.rejectVerification().then((_) {
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
},
|
||||
));
|
||||
break;
|
||||
case KeyVerificationState.waitingAccept:
|
||||
body = Column(
|
||||
children: <Widget>[
|
||||
CircularProgressIndicator(),
|
||||
Container(height: 10),
|
||||
Text(L10n.of(context).waitingPartnerAcceptRequest),
|
||||
],
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
);
|
||||
break;
|
||||
case KeyVerificationState.askSas:
|
||||
var emojiWidgets = <Widget>[];
|
||||
// maybe add a button to switch between the two and only determine default
|
||||
// view for if "emoji" is a present sasType or not?
|
||||
String compareText;
|
||||
if (widget.request.sasTypes.contains('emoji')) {
|
||||
compareText = L10n.of(context).compareEmojiMatch;
|
||||
emojiWidgets =
|
||||
widget.request.sasEmojis.map((e) => _Emoji(e)).toList();
|
||||
} else {
|
||||
compareText = L10n.of(context).compareNumbersMatch;
|
||||
final numbers = widget.request.sasNumbers;
|
||||
emojiWidgets = <Widget>[
|
||||
Text(numbers[0].toString(), style: TextStyle(fontSize: 40)),
|
||||
Text('-', style: TextStyle(fontSize: 40)),
|
||||
Text(numbers[1].toString(), style: TextStyle(fontSize: 40)),
|
||||
Text('-', style: TextStyle(fontSize: 40)),
|
||||
Text(numbers[2].toString(), style: TextStyle(fontSize: 40)),
|
||||
];
|
||||
}
|
||||
body = Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Text(compareText, style: TextStyle(fontSize: 20)),
|
||||
margin: EdgeInsets.only(left: 8.0, right: 8.0),
|
||||
),
|
||||
Container(height: 10),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children:
|
||||
emojiWidgets.map((w) => WidgetSpan(child: w)).toList(),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
);
|
||||
buttons.add(RaisedButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
textColor: Colors.white,
|
||||
child: Text(L10n.of(context).theyMatch),
|
||||
onPressed: () => widget.request.acceptSas(),
|
||||
));
|
||||
buttons.add(RaisedButton(
|
||||
textColor: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
color: Colors.white,
|
||||
child: Text(L10n.of(context).theyDontMatch),
|
||||
onPressed: () => widget.request.rejectSas(),
|
||||
));
|
||||
break;
|
||||
case KeyVerificationState.waitingSas:
|
||||
var acceptText = widget.request.sasTypes.contains('emoji')
|
||||
? L10n.of(context).waitingPartnerEmoji
|
||||
: L10n.of(context).waitingPartnerNumbers;
|
||||
body = Column(
|
||||
children: <Widget>[
|
||||
CircularProgressIndicator(),
|
||||
Container(height: 10),
|
||||
Text(acceptText),
|
||||
],
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
);
|
||||
break;
|
||||
case KeyVerificationState.done:
|
||||
body = Column(
|
||||
children: <Widget>[
|
||||
Icon(Icons.check_circle, color: Colors.green, size: 200.0),
|
||||
Container(height: 10),
|
||||
Text(L10n.of(context).verifySuccess),
|
||||
],
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
);
|
||||
buttons.add(RaisedButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
textColor: Colors.white,
|
||||
child: Text(L10n.of(context).close),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
));
|
||||
break;
|
||||
case KeyVerificationState.error:
|
||||
body = Column(
|
||||
children: <Widget>[
|
||||
Icon(Icons.cancel, color: Colors.red, size: 200.0),
|
||||
Container(height: 10),
|
||||
Text(
|
||||
'Error ${widget.request.canceledCode}: ${widget.request.canceledReason}'),
|
||||
],
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
);
|
||||
buttons.add(RaisedButton(
|
||||
color: Theme.of(context).primaryColor,
|
||||
elevation: 5,
|
||||
textColor: Colors.white,
|
||||
child: Text(L10n.of(context).close),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
));
|
||||
break;
|
||||
}
|
||||
body ??= Text('ERROR: Unknown state ' + widget.request.state.toString());
|
||||
final otherName = profile?.displayname ?? widget.request.userId;
|
||||
var bottom;
|
||||
if (widget.request.deviceId != null) {
|
||||
final deviceName = widget
|
||||
.request
|
||||
.client
|
||||
.userDeviceKeys[widget.request.userId]
|
||||
?.deviceKeys[widget.request.deviceId]
|
||||
?.deviceDisplayName ??
|
||||
'';
|
||||
bottom = PreferredSize(
|
||||
child: Text('$deviceName (${widget.request.deviceId})',
|
||||
style: TextStyle(color: Theme.of(context).textTheme.caption.color)),
|
||||
preferredSize: Size(0.0, 20.0),
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: ListTile(
|
||||
leading: Avatar(profile?.avatarUrl, otherName),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(L10n.of(context).verifyTitle),
|
||||
isThreeLine: otherName != widget.request.userId,
|
||||
subtitle: Column(
|
||||
children: <Widget>[
|
||||
Text(otherName),
|
||||
if (otherName != widget.request.userId)
|
||||
Text(widget.request.userId),
|
||||
],
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
),
|
||||
),
|
||||
elevation: 0,
|
||||
bottom: bottom,
|
||||
),
|
||||
extendBody: true,
|
||||
extendBodyBehindAppBar: true,
|
||||
body: Center(
|
||||
child: body,
|
||||
),
|
||||
persistentFooterButtons: buttons.isEmpty ? null : buttons,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Emoji extends StatelessWidget {
|
||||
final KeyVerificationEmoji emoji;
|
||||
|
||||
_Emoji(this.emoji);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(emoji.emoji, style: TextStyle(fontSize: 50)),
|
||||
Text(emoji.name),
|
||||
Container(height: 10, width: 5),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -38,6 +38,10 @@ class Settings extends StatefulWidget {
|
||||
class _SettingsState extends State<Settings> {
|
||||
Future<dynamic> profileFuture;
|
||||
dynamic profile;
|
||||
Future<bool> crossSigningCachedFuture;
|
||||
bool crossSigningCached;
|
||||
Future<bool> megolmBackupCachedFuture;
|
||||
bool megolmBackupCached;
|
||||
|
||||
void logoutAction(BuildContext context) async {
|
||||
if (await SimpleDialogs(context).askConfirmation() == false) {
|
||||
@ -123,12 +127,65 @@ class _SettingsState extends State<Settings> {
|
||||
setState(() => null);
|
||||
}
|
||||
|
||||
Future<void> requestSSSSCache(BuildContext context) async {
|
||||
final handle = Matrix.of(context).client.encryption.ssss.open();
|
||||
final str = await SimpleDialogs(context).enterText(
|
||||
titleText: L10n.of(context).askSSSSCache,
|
||||
hintText: L10n.of(context).passphraseOrKey,
|
||||
password: true,
|
||||
);
|
||||
if (str != null) {
|
||||
SimpleDialogs(context).showLoadingDialog(context);
|
||||
// make sure the loading spinner shows before we test the keys
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
var valid = false;
|
||||
try {
|
||||
handle.unlock(recoveryKey: str);
|
||||
valid = true;
|
||||
} catch (_) {
|
||||
try {
|
||||
handle.unlock(passphrase: str);
|
||||
valid = true;
|
||||
} catch (_) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
await Navigator.of(context)?.pop();
|
||||
if (valid) {
|
||||
await handle.maybeCacheAll();
|
||||
await SimpleDialogs(context).inform(
|
||||
contentText: L10n.of(context).cachedKeys,
|
||||
);
|
||||
setState(() {
|
||||
crossSigningCachedFuture = null;
|
||||
crossSigningCached = null;
|
||||
megolmBackupCachedFuture = null;
|
||||
megolmBackupCached = null;
|
||||
});
|
||||
} else {
|
||||
await SimpleDialogs(context).inform(
|
||||
contentText: L10n.of(context).incorrectPassphraseOrKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final client = Matrix.of(context).client;
|
||||
profileFuture ??= client.ownProfile;
|
||||
profileFuture.then((p) {
|
||||
profileFuture ??= client.ownProfile.then((p) {
|
||||
if (mounted) setState(() => profile = p);
|
||||
return p;
|
||||
});
|
||||
crossSigningCachedFuture ??=
|
||||
client.encryption.crossSigning.isCached().then((c) {
|
||||
if (mounted) setState(() => crossSigningCached = c);
|
||||
return c;
|
||||
});
|
||||
megolmBackupCachedFuture ??=
|
||||
client.encryption.keyManager.isCached().then((c) {
|
||||
if (mounted) setState(() => megolmBackupCached = c);
|
||||
return c;
|
||||
});
|
||||
return Scaffold(
|
||||
body: NestedScrollView(
|
||||
@ -286,6 +343,110 @@ class _SettingsState extends State<Settings> {
|
||||
onTap: () => logoutAction(context),
|
||||
),
|
||||
Divider(thickness: 1),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context).encryption,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
trailing: Icon(Icons.compare_arrows),
|
||||
title: Text(client.encryption.crossSigning.enabled
|
||||
? L10n.of(context).crossSigningEnabled
|
||||
: L10n.of(context).crossSigningDisabled),
|
||||
subtitle: client.encryption.crossSigning.enabled
|
||||
? Text(client.isUnknownSession
|
||||
? L10n.of(context).unknownSessionVerify
|
||||
: L10n.of(context).sessionVerified +
|
||||
', ' +
|
||||
(crossSigningCached == null
|
||||
? '⌛'
|
||||
: (crossSigningCached
|
||||
? L10n.of(context).keysCached
|
||||
: L10n.of(context).keysMissing)))
|
||||
: null,
|
||||
onTap: () async {
|
||||
if (!client.encryption.crossSigning.enabled) {
|
||||
await SimpleDialogs(context).inform(
|
||||
contentText: L10n.of(context).noCrossSignBootstrap,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (client.isUnknownSession) {
|
||||
final str = await SimpleDialogs(context).enterText(
|
||||
titleText: L10n.of(context).askSSSSVerify,
|
||||
hintText: L10n.of(context).passphraseOrKey,
|
||||
password: true,
|
||||
);
|
||||
if (str != null) {
|
||||
SimpleDialogs(context).showLoadingDialog(context);
|
||||
// make sure the loading spinner shows before we test the keys
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
var valid = false;
|
||||
try {
|
||||
await client.encryption.crossSigning
|
||||
.selfSign(recoveryKey: str);
|
||||
valid = true;
|
||||
} catch (_) {
|
||||
try {
|
||||
await client.encryption.crossSigning
|
||||
.selfSign(passphrase: str);
|
||||
valid = true;
|
||||
} catch (_) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
await Navigator.of(context)?.pop();
|
||||
if (valid) {
|
||||
await SimpleDialogs(context).inform(
|
||||
contentText: L10n.of(context).verifiedSession,
|
||||
);
|
||||
setState(() {
|
||||
crossSigningCachedFuture = null;
|
||||
crossSigningCached = null;
|
||||
megolmBackupCachedFuture = null;
|
||||
megolmBackupCached = null;
|
||||
});
|
||||
} else {
|
||||
await SimpleDialogs(context).inform(
|
||||
contentText: L10n.of(context).incorrectPassphraseOrKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(await client.encryption.crossSigning.isCached())) {
|
||||
await requestSSSSCache(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
trailing: Icon(Icons.wb_cloudy),
|
||||
title: Text(client.encryption.keyManager.enabled
|
||||
? L10n.of(context).onlineKeyBackupEnabled
|
||||
: L10n.of(context).onlineKeyBackupDisabled),
|
||||
subtitle: client.encryption.keyManager.enabled
|
||||
? Text(megolmBackupCached == null
|
||||
? '⌛'
|
||||
: (megolmBackupCached
|
||||
? L10n.of(context).keysCached
|
||||
: L10n.of(context).keysMissing))
|
||||
: null,
|
||||
onTap: () async {
|
||||
if (!client.encryption.keyManager.enabled) {
|
||||
await SimpleDialogs(context).inform(
|
||||
contentText: L10n.of(context).noMegolmBootstrap,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!(await client.encryption.keyManager.isCached())) {
|
||||
await requestSSSSCache(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
Divider(thickness: 1),
|
||||
ListTile(
|
||||
title: Text(
|
||||
L10n.of(context).about,
|
||||
|
58
pubspec.lock
58
pubspec.lock
@ -29,6 +29,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.4"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -36,6 +43,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
base58check:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: base58check
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -127,6 +141,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
encrypt:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: encrypt
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
encrypted_moor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -136,19 +157,12 @@ packages:
|
||||
url: "https://github.com/simolus3/moor.git"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
famedlysdk:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: b8c6decafc52cbf5c09288c6c6dde62b62ae978f
|
||||
resolved-ref: b8c6decafc52cbf5c09288c6c6dde62b62ae978f
|
||||
ref: "8e2c8b0d1146e99e80ef5f5bf4b4c8e378772b06"
|
||||
resolved-ref: "8e2c8b0d1146e99e80ef5f5bf4b4c8e378772b06"
|
||||
url: "https://gitlab.com/famedly/famedlysdk.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
@ -487,10 +501,10 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: "1.x.y"
|
||||
resolved-ref: f66975bd1b5cb1865eba5efe6e3a392aa5e396a5
|
||||
resolved-ref: "8e4fcccff7a2d4d0bd5142964db092bf45061905"
|
||||
url: "https://gitlab.com/famedly/libraries/dart-olm.git"
|
||||
source: git
|
||||
version: "1.1.1"
|
||||
version: "1.2.0"
|
||||
open_file:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -512,13 +526,20 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.10"
|
||||
password_hash:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: password_hash
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
version: "1.6.4"
|
||||
path_drawing:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -596,6 +617,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
random_string:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -733,21 +761,21 @@ packages:
|
||||
name: test
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.14.7"
|
||||
version: "1.14.4"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.16"
|
||||
version: "0.2.15"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.7"
|
||||
version: "0.3.4"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -27,7 +27,7 @@ dependencies:
|
||||
famedlysdk:
|
||||
git:
|
||||
url: https://gitlab.com/famedly/famedlysdk.git
|
||||
ref: b8c6decafc52cbf5c09288c6c6dde62b62ae978f
|
||||
ref: 8e2c8b0d1146e99e80ef5f5bf4b4c8e378772b06
|
||||
|
||||
localstorage: ^3.0.1+4
|
||||
bubble: ^1.1.9+1
|
||||
|
Loading…
Reference in New Issue
Block a user