mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 01:12:12 +00:00
Merge pull request #405 from twonlyapp/dev
Some checks failed
Publish on Github / build_and_publish (push) Has been cancelled
Some checks failed
Publish on Github / build_and_publish (push) Has been cancelled
- Improved: Make contact avatars clickable - Fix: Messages occasionally not received until app restart - Fix: Complete setup would sometimes get stuck
This commit is contained in:
commit
d86252d800
40 changed files with 920 additions and 461 deletions
|
|
@ -1,5 +1,11 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.2.9
|
||||||
|
|
||||||
|
- Improved: Make contact avatars clickable
|
||||||
|
- Fix: Messages occasionally not received until app restart
|
||||||
|
- Fix: Complete setup would sometimes get stuck
|
||||||
|
|
||||||
## 0.2.8
|
## 0.2.8
|
||||||
|
|
||||||
- Fix: App did not launch sometimes on Android
|
- Fix: App did not launch sometimes on Android
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,21 @@ Future<void> postStartupTasks() async {
|
||||||
unawaited(finishStartedPreprocessing());
|
unawaited(finishStartedPreprocessing());
|
||||||
unawaited(createPushAvatars());
|
unawaited(createPushAvatars());
|
||||||
|
|
||||||
|
if (userService.currentUser.userDiscoveryInitializationError) {
|
||||||
|
unawaited(() async {
|
||||||
|
try {
|
||||||
|
await UserDiscoveryService.initializeOrUpdate(
|
||||||
|
threshold: userService.currentUser.userDiscoveryThreshold,
|
||||||
|
sharePromotion: userService.currentUser.userDiscoverySharePromotion,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(
|
||||||
|
'Failed to retry UserDiscovery initialization on startup: $e',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 10));
|
await Future.delayed(const Duration(seconds: 10));
|
||||||
unawaited(initializeBackgroundTaskManager());
|
unawaited(initializeBackgroundTaskManager());
|
||||||
// 3. Delayed tasks (Wait for app to settle)
|
// 3. Delayed tasks (Wait for app to settle)
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,12 @@ class UserDiscoveryCallbacks {
|
||||||
static Future<Uint8List?> signData(
|
static Future<Uint8List?> signData(
|
||||||
Uint8List inputData,
|
Uint8List inputData,
|
||||||
) async {
|
) async {
|
||||||
|
Log.info('UserDiscoveryCallbacks: signData started');
|
||||||
var privKey = (await getSignalIdentityKeyPair())?.getPrivateKey();
|
var privKey = (await getSignalIdentityKeyPair())?.getPrivateKey();
|
||||||
if (privKey == null) return null;
|
if (privKey == null) {
|
||||||
|
Log.error('UserDiscoveryCallbacks: signData failed, privKey is null');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
final random = getRandomUint8List(32);
|
final random = getRandomUint8List(32);
|
||||||
final signature = sign(
|
final signature = sign(
|
||||||
privKey.serialize(),
|
privKey.serialize(),
|
||||||
|
|
@ -25,6 +29,7 @@ class UserDiscoveryCallbacks {
|
||||||
random,
|
random,
|
||||||
);
|
);
|
||||||
privKey = null;
|
privKey = null;
|
||||||
|
Log.info('UserDiscoveryCallbacks: signData finished');
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -332,4 +332,18 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
|
|
||||||
return query.map((row) => row.readTable(groups)).watch();
|
return query.map((row) => row.readTable(groups)).watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Group>> getGroupsForMember(int contactId) {
|
||||||
|
final query =
|
||||||
|
select(groups).join([
|
||||||
|
innerJoin(
|
||||||
|
groupMembers,
|
||||||
|
groupMembers.groupId.equalsExp(groups.groupId),
|
||||||
|
),
|
||||||
|
])..where(
|
||||||
|
groupMembers.contactId.equals(contactId),
|
||||||
|
);
|
||||||
|
|
||||||
|
return query.map((row) => row.readTable(groups)).get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,25 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> getTransferredTrustVerificationsCount() async {
|
||||||
|
final kv = keyVerifications;
|
||||||
|
final ur = userDiscoveryUserRelations;
|
||||||
|
|
||||||
|
final query = selectOnly(ur, distinct: true)
|
||||||
|
..addColumns([ur.announcedUserId])
|
||||||
|
..join([
|
||||||
|
innerJoin(contacts, contacts.userId.equalsExp(ur.fromContactId)),
|
||||||
|
innerJoin(kv, kv.contactId.equalsExp(ur.fromContactId)),
|
||||||
|
])
|
||||||
|
..where(
|
||||||
|
ur.publicKeyVerifiedTimestamp.isNotNull() &
|
||||||
|
ur.announcedUserId.equalsExp(ur.fromContactId).not(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final rows = await query.get();
|
||||||
|
return rows.length;
|
||||||
|
}
|
||||||
|
|
||||||
Stream<VerificationStatus> watchAllGroupMembersVerified(String groupId) {
|
Stream<VerificationStatus> watchAllGroupMembersVerified(String groupId) {
|
||||||
final gm = groupMembers;
|
final gm = groupMembers;
|
||||||
final directKv = alias(keyVerifications, 'directKv');
|
final directKv = alias(keyVerifications, 'directKv');
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ enum GroupActionType {
|
||||||
demoteToMember,
|
demoteToMember,
|
||||||
updatedGroupName,
|
updatedGroupName,
|
||||||
changeDisplayMaxTime,
|
changeDisplayMaxTime,
|
||||||
|
updatedContactUsername,
|
||||||
|
updatedContactDisplayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataClassName('GroupHistory')
|
@DataClassName('GroupHistory')
|
||||||
|
|
|
||||||
|
|
@ -2965,6 +2965,102 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Approve'**
|
/// **'Approve'**
|
||||||
String get contactUserDiscoveryManualApprovalApprove;
|
String get contactUserDiscoveryManualApprovalApprove;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName1.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'james'**
|
||||||
|
String get exampleUserName1;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName2.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'mary'**
|
||||||
|
String get exampleUserName2;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName3.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'john'**
|
||||||
|
String get exampleUserName3;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName4.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'patricia'**
|
||||||
|
String get exampleUserName4;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName5.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'robert'**
|
||||||
|
String get exampleUserName5;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName6.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'jennifer'**
|
||||||
|
String get exampleUserName6;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName7.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'michael'**
|
||||||
|
String get exampleUserName7;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName8.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'linda'**
|
||||||
|
String get exampleUserName8;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName9.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'william'**
|
||||||
|
String get exampleUserName9;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName10.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'lena'**
|
||||||
|
String get exampleUserName10;
|
||||||
|
|
||||||
|
/// No description provided for @exampleUserName11.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'david'**
|
||||||
|
String get exampleUserName11;
|
||||||
|
|
||||||
|
/// No description provided for @exampleJane.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'jane'**
|
||||||
|
String get exampleJane;
|
||||||
|
|
||||||
|
/// No description provided for @back.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Back'**
|
||||||
|
String get back;
|
||||||
|
|
||||||
|
/// No description provided for @onboardingExampleLabel.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Example'**
|
||||||
|
String get onboardingExampleLabel;
|
||||||
|
|
||||||
|
/// No description provided for @makerChangedUsername.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{maker} changed their username from {oldName} to {newName}.'**
|
||||||
|
String makerChangedUsername(Object maker, Object oldName, Object newName);
|
||||||
|
|
||||||
|
/// No description provided for @makerChangedDisplayName.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{maker} changed their display name from {oldName} to {newName}.'**
|
||||||
|
String makerChangedDisplayName(Object maker, Object oldName, Object newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1669,4 +1669,56 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contactUserDiscoveryManualApprovalApprove => 'Freigeben';
|
String get contactUserDiscoveryManualApprovalApprove => 'Freigeben';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName1 => 'max_mustermann';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName2 => 'erika_musterfrau';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName3 => 'hans';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName4 => 'petra';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName5 => 'klaus';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName6 => 'sabine';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName7 => 'stefan';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName8 => 'monika';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName9 => 'christian';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName10 => 'lena';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName11 => 'david';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleJane => 'erika';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get back => 'Zurück';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get onboardingExampleLabel => 'Beispiel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String makerChangedUsername(Object maker, Object oldName, Object newName) {
|
||||||
|
return '$maker hat seinen Benutzernamen von $oldName zu $newName geändert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String makerChangedDisplayName(Object maker, Object oldName, Object newName) {
|
||||||
|
return '$maker hat seinen Anzeigenamen von $oldName zu $newName geändert.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1654,4 +1654,56 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get contactUserDiscoveryManualApprovalApprove => 'Approve';
|
String get contactUserDiscoveryManualApprovalApprove => 'Approve';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName1 => 'james';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName2 => 'mary';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName3 => 'john';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName4 => 'patricia';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName5 => 'robert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName6 => 'jennifer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName7 => 'michael';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName8 => 'linda';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName9 => 'william';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName10 => 'lena';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleUserName11 => 'david';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exampleJane => 'jane';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get back => 'Back';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get onboardingExampleLabel => 'Example';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String makerChangedUsername(Object maker, Object oldName, Object newName) {
|
||||||
|
return '$maker changed their username from $oldName to $newName.';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String makerChangedDisplayName(Object maker, Object oldName, Object newName) {
|
||||||
|
return '$maker changed their display name from $oldName to $newName.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 03bf220400bf35002c77e153768bd0f963a97d89
|
Subproject commit 781626f66c5f992ffad861abb7b4937f82319392
|
||||||
|
|
@ -109,6 +109,9 @@ class UserData {
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: true)
|
||||||
bool userDiscoverySharePromotion = true;
|
bool userDiscoverySharePromotion = true;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool userDiscoveryInitializationError = false;
|
||||||
|
|
||||||
// -- Custom DATA --
|
// -- Custom DATA --
|
||||||
|
|
||||||
@JsonKey(defaultValue: 100_000)
|
@JsonKey(defaultValue: 100_000)
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> onClosed() async {
|
Future<void> onClosed() async {
|
||||||
|
Log.info('websocket connection closed');
|
||||||
_channel = null;
|
_channel = null;
|
||||||
isAuthenticated = false;
|
isAuthenticated = false;
|
||||||
_connectionStateController.add(false);
|
_connectionStateController.add(false);
|
||||||
|
|
@ -249,7 +250,7 @@ class ApiService {
|
||||||
completer.complete(msg);
|
completer.complete(msg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await handleServerMessage(msg);
|
unawaited(handleServerMessage(msg));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('Error parsing the servers message: $e');
|
Log.error('Error parsing the servers message: $e');
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'dart:convert';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||||
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart' hide Message;
|
import 'package:twonly/src/database/twonly.db.dart' hide Message;
|
||||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
import 'package:twonly/src/model/protobuf/client/generated/messages.pb.dart';
|
||||||
import 'package:twonly/src/services/api/messages.api.dart';
|
import 'package:twonly/src/services/api/messages.api.dart';
|
||||||
|
|
@ -126,6 +127,44 @@ Future<void> handleContactUpdate(
|
||||||
if (contactUpdate.hasDisplayName() &&
|
if (contactUpdate.hasDisplayName() &&
|
||||||
contactUpdate.hasUsername() &&
|
contactUpdate.hasUsername() &&
|
||||||
senderProfileCounter != null) {
|
senderProfileCounter != null) {
|
||||||
|
final contact = await twonlyDB.contactsDao
|
||||||
|
.getContactByUserId(fromUserId)
|
||||||
|
.getSingleOrNull();
|
||||||
|
|
||||||
|
if (contact != null) {
|
||||||
|
final sharedGroups = await twonlyDB.groupsDao.getGroupsForMember(
|
||||||
|
fromUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (contact.username != contactUpdate.username) {
|
||||||
|
for (final group in sharedGroups) {
|
||||||
|
await twonlyDB.groupsDao.insertGroupAction(
|
||||||
|
GroupHistoriesCompanion(
|
||||||
|
groupId: Value(group.groupId),
|
||||||
|
type: const Value(GroupActionType.updatedContactUsername),
|
||||||
|
contactId: Value(fromUserId),
|
||||||
|
oldGroupName: Value('@${contact.username}'),
|
||||||
|
newGroupName: Value('@${contactUpdate.username}'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contact.displayName != contactUpdate.displayName) {
|
||||||
|
for (final group in sharedGroups) {
|
||||||
|
await twonlyDB.groupsDao.insertGroupAction(
|
||||||
|
GroupHistoriesCompanion(
|
||||||
|
groupId: Value(group.groupId),
|
||||||
|
type: const Value(GroupActionType.updatedContactDisplayName),
|
||||||
|
contactId: Value(fromUserId),
|
||||||
|
oldGroupName: Value(contact.displayName ?? ''),
|
||||||
|
newGroupName: Value(contactUpdate.displayName),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await twonlyDB.contactsDao.updateContact(
|
await twonlyDB.contactsDao.updateContact(
|
||||||
fromUserId,
|
fromUserId,
|
||||||
ContactsCompanion(
|
ContactsCompanion(
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,8 @@ Future<void> handleGroupUpdate(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case GroupActionType.createdGroup:
|
case GroupActionType.createdGroup:
|
||||||
|
case GroupActionType.updatedContactUsername:
|
||||||
|
case GroupActionType.updatedContactDisplayName:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
Receipt? receipt,
|
Receipt? receipt,
|
||||||
bool onlyReturnEncryptedData = false,
|
bool onlyReturnEncryptedData = false,
|
||||||
bool blocking = true,
|
bool blocking = true,
|
||||||
|
bool useLock = true,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
if (receiptId == null && receipt == null) return null;
|
if (receiptId == null && receipt == null) return null;
|
||||||
|
|
@ -132,6 +133,7 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
final cipherText = await signalEncryptMessage(
|
final cipherText = await signalEncryptMessage(
|
||||||
receipt.contactId,
|
receipt.contactId,
|
||||||
Uint8List.fromList(message.encryptedContent),
|
Uint8List.fromList(message.encryptedContent),
|
||||||
|
useLock: useLock,
|
||||||
);
|
);
|
||||||
if (cipherText == null) {
|
if (cipherText == null) {
|
||||||
Log.error('Could not encrypt the message. Aborting and trying again.');
|
Log.error('Could not encrypt the message. Aborting and trying again.');
|
||||||
|
|
@ -336,6 +338,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
bool blocking = true,
|
bool blocking = true,
|
||||||
String? messageId,
|
String? messageId,
|
||||||
bool onlySendIfNoReceiptsAreOpen = false,
|
bool onlySendIfNoReceiptsAreOpen = false,
|
||||||
|
bool useLock = true,
|
||||||
}) async {
|
}) async {
|
||||||
if (onlySendIfNoReceiptsAreOpen) {
|
if (onlySendIfNoReceiptsAreOpen) {
|
||||||
final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact(
|
final openReceipts = await twonlyDB.receiptsDao.getReceiptCountForContact(
|
||||||
|
|
@ -397,6 +400,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
receipt: receipt,
|
receipt: receipt,
|
||||||
onlyReturnEncryptedData: onlyReturnEncryptedData,
|
onlyReturnEncryptedData: onlyReturnEncryptedData,
|
||||||
blocking: blocking,
|
blocking: blocking,
|
||||||
|
useLock: useLock,
|
||||||
);
|
);
|
||||||
if (!blocking) {
|
if (!blocking) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ final lockHandleServerMessage = Mutex();
|
||||||
|
|
||||||
Future<void> handleServerMessage(server.ServerToClient msg) async {
|
Future<void> handleServerMessage(server.ServerToClient msg) async {
|
||||||
return lockHandleServerMessage.protect(() async {
|
return lockHandleServerMessage.protect(() async {
|
||||||
|
Log.info('Processing a message from the server.');
|
||||||
|
|
||||||
/// Returns means, that the server can delete the message from the server.
|
/// Returns means, that the server can delete the message from the server.
|
||||||
final ok = client.Response_Ok()..none = true;
|
final ok = client.Response_Ok()..none = true;
|
||||||
var response = client.Response()..ok = ok;
|
var response = client.Response()..ok = ok;
|
||||||
|
|
@ -48,8 +50,12 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
||||||
if (msg.v0.hasRequestNewPreKeys()) {
|
if (msg.v0.hasRequestNewPreKeys()) {
|
||||||
response = await handleRequestNewPreKey();
|
response = await handleRequestNewPreKey();
|
||||||
} else if (msg.v0.hasNewMessage()) {
|
} else if (msg.v0.hasNewMessage()) {
|
||||||
|
Log.info('Got 1 message from the server.');
|
||||||
await handleClient2ClientMessage(msg.v0.newMessage);
|
await handleClient2ClientMessage(msg.v0.newMessage);
|
||||||
} else if (msg.v0.hasNewMessages()) {
|
} else if (msg.v0.hasNewMessages()) {
|
||||||
|
Log.info(
|
||||||
|
'Got ${msg.v0.newMessages.newMessages.length} messages from the server.',
|
||||||
|
);
|
||||||
for (final newMessage in msg.v0.newMessages.newMessages) {
|
for (final newMessage in msg.v0.newMessages.newMessages) {
|
||||||
try {
|
try {
|
||||||
await handleClient2ClientMessage(newMessage);
|
await handleClient2ClientMessage(newMessage);
|
||||||
|
|
@ -70,13 +76,12 @@ Future<void> handleServerMessage(server.ServerToClient msg) async {
|
||||||
|
|
||||||
await apiService.sendResponse(ClientToServer()..v0 = v0);
|
await apiService.sendResponse(ClientToServer()..v0 = v0);
|
||||||
AppState.gotMessageFromServer = true;
|
AppState.gotMessageFromServer = true;
|
||||||
|
Log.info('Message from server proccessed.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
|
DateTime lastPushKeyRequest = clock.now().subtract(const Duration(hours: 1));
|
||||||
|
|
||||||
Mutex protectReceiptCheck = Mutex();
|
|
||||||
|
|
||||||
Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
final body = Uint8List.fromList(newMessage.body);
|
final body = Uint8List.fromList(newMessage.body);
|
||||||
final fromUserId = newMessage.fromUserId.toInt();
|
final fromUserId = newMessage.fromUserId.toInt();
|
||||||
|
|
@ -84,15 +89,15 @@ Future<void> handleClient2ClientMessage(NewMessage newMessage) async {
|
||||||
final message = Message.fromBuffer(body);
|
final message = Message.fromBuffer(body);
|
||||||
final receiptId = message.receiptId;
|
final receiptId = message.receiptId;
|
||||||
|
|
||||||
final isDuplicated = await protectReceiptCheck.protect(() async {
|
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
|
||||||
if (await twonlyDB.receiptsDao.isDuplicated(receiptId)) {
|
return;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
await twonlyDB.receiptsDao.gotReceipt(receiptId);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDuplicated) {
|
try {
|
||||||
|
await twonlyDB.receiptsDao.gotReceipt(receiptId);
|
||||||
|
Log.info('Got a message with receiptId $receiptId');
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ Future<bool> initBackgroundExecution() async {
|
||||||
|
|
||||||
final Mutex _keyValueMutex = Mutex();
|
final Mutex _keyValueMutex = Mutex();
|
||||||
|
|
||||||
|
// ignore: unreachable_from_main
|
||||||
Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
Future<void> handlePeriodicTask({int lastExecutionInSecondsLimit = 120}) async {
|
||||||
final shouldBeExecuted = await exclusiveAccess(
|
final shouldBeExecuted = await exclusiveAccess(
|
||||||
lockName: 'periodic_task',
|
lockName: 'periodic_task',
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,8 @@ Future<void> compressAndOverlayVideo(MediaFileService media) async {
|
||||||
VideoSegment(video: EditorVideo.file(media.originalPath)),
|
VideoSegment(video: EditorVideo.file(media.originalPath)),
|
||||||
],
|
],
|
||||||
imageLayers: [
|
imageLayers: [
|
||||||
ImageLayer(image: EditorLayerImage.file(media.overlayImagePath)),
|
if (media.overlayImagePath.existsSync())
|
||||||
|
ImageLayer(image: EditorLayerImage.file(media.overlayImagePath)),
|
||||||
],
|
],
|
||||||
enableAudio: !media.removeAudio,
|
enableAudio: !media.removeAudio,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ class MediaFileService {
|
||||||
final group = await twonlyDB.groupsDao.getGroup(
|
final group = await twonlyDB.groupsDao.getGroup(
|
||||||
message.groupId,
|
message.groupId,
|
||||||
);
|
);
|
||||||
if (group != null) {
|
if (group != null && !group.isDirectChat) {
|
||||||
delete = false;
|
delete = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,31 @@ import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
Future<CiphertextMessage?> signalEncryptMessage(
|
Future<CiphertextMessage?> signalEncryptMessage(
|
||||||
|
int target,
|
||||||
|
Uint8List plaintextContent, {
|
||||||
|
bool useLock = true,
|
||||||
|
}) async {
|
||||||
|
if (useLock) {
|
||||||
|
return lockingSignalProtocol.protect<CiphertextMessage?>(() async {
|
||||||
|
return _signalEncryptMessage(target, plaintextContent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _signalEncryptMessage(target, plaintextContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<CiphertextMessage?> _signalEncryptMessage(
|
||||||
int target,
|
int target,
|
||||||
Uint8List plaintextContent,
|
Uint8List plaintextContent,
|
||||||
) async {
|
) async {
|
||||||
return lockingSignalProtocol.protect<CiphertextMessage?>(() async {
|
try {
|
||||||
try {
|
final signalStore = (await getSignalStore())!;
|
||||||
final signalStore = (await getSignalStore())!;
|
final address = getSignalAddress(target);
|
||||||
final address = getSignalAddress(target);
|
final session = SessionCipher.fromStore(signalStore, address);
|
||||||
final session = SessionCipher.fromStore(signalStore, address);
|
return await session.encrypt(plaintextContent);
|
||||||
return await session.encrypt(plaintextContent);
|
} catch (e) {
|
||||||
} catch (e) {
|
Log.error(e.toString());
|
||||||
Log.error(e.toString());
|
return null;
|
||||||
return null;
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
|
Future<(EncryptedContent?, PlaintextContent_DecryptionErrorMessage_Type?)>
|
||||||
|
|
@ -67,8 +78,9 @@ signalDecryptMessage(
|
||||||
Log.info(e.toString());
|
Log.info(e.toString());
|
||||||
return (null, null);
|
return (null, null);
|
||||||
} on InvalidMessageException catch (e) {
|
} on InvalidMessageException catch (e) {
|
||||||
|
Log.warn(e);
|
||||||
if (!resyncedUsers.contains(fromUserId)) {
|
if (!resyncedUsers.contains(fromUserId)) {
|
||||||
if (await handleSessionResync(fromUserId)) {
|
if (await handleSessionResync(fromUserId, useLock: false)) {
|
||||||
// This flag prevents from resyncing the session the client received multiple new
|
// This flag prevents from resyncing the session the client received multiple new
|
||||||
// messages from the server he could not decrypt
|
// messages from the server he could not decrypt
|
||||||
resyncedUsers.add(fromUserId);
|
resyncedUsers.add(fromUserId);
|
||||||
|
|
@ -81,10 +93,10 @@ signalDecryptMessage(
|
||||||
type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
type: EncryptedContent_ErrorMessages_Type.SESSION_OUT_OF_SYNC,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
useLock: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.warn(e);
|
|
||||||
return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN);
|
return (null, PlaintextContent_DecryptionErrorMessage_Type.UNKNOWN);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
|
|
|
||||||
|
|
@ -91,9 +91,14 @@ Future<SignalIdentity?> getSignalIdentity() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List> getUserPublicKey() async {
|
Future<Uint8List> getUserPublicKey() async {
|
||||||
|
Log.info('getUserPublicKey: getting identity');
|
||||||
final signalIdentity = (await getSignalIdentity())!;
|
final signalIdentity = (await getSignalIdentity())!;
|
||||||
|
Log.info('getUserPublicKey: getting signal store');
|
||||||
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
final signalStore = await getSignalStoreFromIdentity(signalIdentity);
|
||||||
return (await signalStore.getIdentityKeyPair()).getPublicKey().serialize();
|
Log.info('getUserPublicKey: getting key pair');
|
||||||
|
final keyPair = await signalStore.getIdentityKeyPair();
|
||||||
|
Log.info('getUserPublicKey: serializing public key');
|
||||||
|
return keyPair.getPublicKey().serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createIfNotExistsSignalIdentity() async {
|
Future<void> createIfNotExistsSignalIdentity() async {
|
||||||
|
|
|
||||||
|
|
@ -8,73 +8,83 @@ import 'package:twonly/src/services/signal/protocol_state.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
Future<bool> processSignalUserData(Response_UserData userData) async {
|
Future<bool> processSignalUserData(
|
||||||
return lockingSignalProtocol.protect(() async {
|
Response_UserData userData, {
|
||||||
final SignalProtocolStore? signalStore = await getSignalStore();
|
bool useLock = true,
|
||||||
|
}) async {
|
||||||
|
if (useLock) {
|
||||||
|
return lockingSignalProtocol.protect(() async {
|
||||||
|
return _processSignalUserData(userData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _processSignalUserData(userData);
|
||||||
|
}
|
||||||
|
|
||||||
if (signalStore == null) {
|
Future<bool> _processSignalUserData(Response_UserData userData) async {
|
||||||
return false;
|
final SignalProtocolStore? signalStore = await getSignalStore();
|
||||||
}
|
|
||||||
|
|
||||||
final targetAddress = getSignalAddress(userData.userId.toInt());
|
if (signalStore == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
final sessionBuilder = SessionBuilder.fromSignalStore(
|
final targetAddress = getSignalAddress(userData.userId.toInt());
|
||||||
signalStore,
|
|
||||||
targetAddress,
|
|
||||||
);
|
|
||||||
|
|
||||||
ECPublicKey? tempPrePublicKey;
|
final sessionBuilder = SessionBuilder.fromSignalStore(
|
||||||
int? tempPreKeyId;
|
signalStore,
|
||||||
|
targetAddress,
|
||||||
|
);
|
||||||
|
|
||||||
if (userData.prekeys.isNotEmpty) {
|
ECPublicKey? tempPrePublicKey;
|
||||||
tempPrePublicKey = Curve.decodePoint(
|
int? tempPreKeyId;
|
||||||
DjbECPublicKey(
|
|
||||||
Uint8List.fromList(userData.prekeys.first.prekey),
|
|
||||||
).serialize(),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
tempPreKeyId = userData.prekeys.first.id.toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
final tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
if (userData.prekeys.isNotEmpty) {
|
||||||
|
tempPrePublicKey = Curve.decodePoint(
|
||||||
final tempSignedPreKeyPublic = Curve.decodePoint(
|
DjbECPublicKey(
|
||||||
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(),
|
Uint8List.fromList(userData.prekeys.first.prekey),
|
||||||
|
).serialize(),
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
|
tempPreKeyId = userData.prekeys.first.id.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
final tempSignedPreKeySignature = Uint8List.fromList(
|
final tempSignedPreKeyId = userData.signedPrekeyId.toInt();
|
||||||
userData.signedPrekeySignature,
|
|
||||||
);
|
|
||||||
|
|
||||||
final tempIdentityKey = IdentityKey(
|
final tempSignedPreKeyPublic = Curve.decodePoint(
|
||||||
Curve.decodePoint(
|
DjbECPublicKey(Uint8List.fromList(userData.signedPrekey)).serialize(),
|
||||||
DjbECPublicKey(
|
1,
|
||||||
Uint8List.fromList(userData.publicIdentityKey),
|
);
|
||||||
).serialize(),
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final preKeyBundle = PreKeyBundle(
|
final tempSignedPreKeySignature = Uint8List.fromList(
|
||||||
userData.registrationId.toInt(),
|
userData.signedPrekeySignature,
|
||||||
defaultDeviceId,
|
);
|
||||||
tempPreKeyId,
|
|
||||||
tempPrePublicKey,
|
|
||||||
tempSignedPreKeyId,
|
|
||||||
tempSignedPreKeyPublic,
|
|
||||||
tempSignedPreKeySignature,
|
|
||||||
tempIdentityKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
final tempIdentityKey = IdentityKey(
|
||||||
await sessionBuilder.processPreKeyBundle(preKeyBundle);
|
Curve.decodePoint(
|
||||||
return true;
|
DjbECPublicKey(
|
||||||
} catch (e) {
|
Uint8List.fromList(userData.publicIdentityKey),
|
||||||
Log.error('could not process pre key bundle: $e');
|
).serialize(),
|
||||||
return false;
|
1,
|
||||||
}
|
),
|
||||||
});
|
);
|
||||||
|
|
||||||
|
final preKeyBundle = PreKeyBundle(
|
||||||
|
userData.registrationId.toInt(),
|
||||||
|
defaultDeviceId,
|
||||||
|
tempPreKeyId,
|
||||||
|
tempPrePublicKey,
|
||||||
|
tempSignedPreKeyId,
|
||||||
|
tempSignedPreKeyPublic,
|
||||||
|
tempSignedPreKeySignature,
|
||||||
|
tempIdentityKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sessionBuilder.processPreKeyBundle(preKeyBundle);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('could not process pre key bundle: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> getPublicKeyFromContact(int contactId) async {
|
Future<Uint8List?> getPublicKeyFromContact(int contactId) async {
|
||||||
|
|
@ -96,11 +106,14 @@ Future<Uint8List?> getPublicKeyFromContact(int contactId) async {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> handleSessionResync(int fromUserId) async {
|
Future<bool> handleSessionResync(
|
||||||
|
int fromUserId, {
|
||||||
|
bool useLock = true,
|
||||||
|
}) async {
|
||||||
final userData = await apiService.getUserById(fromUserId);
|
final userData = await apiService.getUserById(fromUserId);
|
||||||
if (userData != null) {
|
if (userData != null) {
|
||||||
Log.info('Got new session data from the server to re-sync the session');
|
Log.info('Got new session data from the server to re-sync the session');
|
||||||
return processSignalUserData(userData);
|
return processSignalUserData(userData, useLock: useLock);
|
||||||
}
|
}
|
||||||
Log.info('Could not download userdata from the server.');
|
Log.info('Could not download userdata from the server.');
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -75,22 +75,27 @@ class UserDiscoveryService {
|
||||||
required int threshold,
|
required int threshold,
|
||||||
required bool sharePromotion,
|
required bool sharePromotion,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
Log.info('UserDiscoveryService: initializeOrUpdate started');
|
||||||
await FlutterUserDiscovery.initializeOrUpdate(
|
final userId = userService.currentUser.userId;
|
||||||
threshold: threshold,
|
final publicKey = await getUserPublicKey();
|
||||||
userId: userService.currentUser.userId,
|
Log.info('UserDiscoveryService: initializing Rust bridge');
|
||||||
publicKey: await getUserPublicKey(),
|
await FlutterUserDiscovery.initializeOrUpdate(
|
||||||
sharePromotion: sharePromotion,
|
threshold: threshold,
|
||||||
);
|
userId: userId,
|
||||||
await UserService.update(
|
publicKey: publicKey,
|
||||||
(u) => u
|
sharePromotion: sharePromotion,
|
||||||
..isUserDiscoveryEnabled = true
|
).timeout(const Duration(seconds: 8));
|
||||||
..userDiscoverySharePromotion = sharePromotion
|
Log.info(
|
||||||
..userDiscoveryThreshold = threshold,
|
'UserDiscoveryService: Rust bridge initialized, updating UserService',
|
||||||
);
|
);
|
||||||
} catch (e) {
|
await UserService.update(
|
||||||
Log.error(e);
|
(u) => u
|
||||||
}
|
..isUserDiscoveryEnabled = true
|
||||||
|
..userDiscoverySharePromotion = sharePromotion
|
||||||
|
..userDiscoveryThreshold = threshold
|
||||||
|
..userDiscoveryInitializationError = false,
|
||||||
|
);
|
||||||
|
Log.info('UserDiscoveryService: initializeOrUpdate finished');
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Uint8List?> getCurrentVersion() async {
|
static Future<Uint8List?> getCurrentVersion() async {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ Future<void> handleUserStudyUpload() async {
|
||||||
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
final contacts = await twonlyDB.contactsDao.getAllContacts();
|
||||||
final verifications = await twonlyDB.keyVerificationDao
|
final verifications = await twonlyDB.keyVerificationDao
|
||||||
.getFirstVerificationTypeByContacts();
|
.getFirstVerificationTypeByContacts();
|
||||||
|
final udVerifiedByContactsCount = await twonlyDB.keyVerificationDao
|
||||||
|
.getTransferredTrustVerificationsCount();
|
||||||
|
|
||||||
final udFriendsShared = await twonlyDB.contactsDao
|
final udFriendsShared = await twonlyDB.contactsDao
|
||||||
.getContactsAnnouncedViaUserDiscovery();
|
.getContactsAnnouncedViaUserDiscovery();
|
||||||
|
|
@ -81,6 +83,7 @@ Future<void> handleUserStudyUpload() async {
|
||||||
|
|
||||||
'user_study_count_new_friends_via_suggestion':
|
'user_study_count_new_friends_via_suggestion':
|
||||||
userService.currentUser.userStudyCountNewFriendsViaSuggestion,
|
userService.currentUser.userStudyCountNewFriendsViaSuggestion,
|
||||||
|
'user_discovery_count_verified_by_contacts': udVerifiedByContactsCount,
|
||||||
|
|
||||||
'accepted_contacts': contacts.where((c) => c.accepted).length,
|
'accepted_contacts': contacts.where((c) => c.accepted).length,
|
||||||
'verified_contacts': verifications.length,
|
'verified_contacts': verifications.length,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ final ThemeData darkTheme = ThemeData.dark().copyWith(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
seedColor: const Color(0xFF57CC99),
|
seedColor: const Color(0xFF57CC99),
|
||||||
surface: const Color.fromARGB(255, 20, 18, 23),
|
surface: const Color.fromARGB(255, 20, 18, 23),
|
||||||
surfaceContainer: const Color.fromARGB(255, 33, 30, 39),
|
surfaceContainer: const Color.fromARGB(255, 45, 41, 54),
|
||||||
|
surfaceContainerLow: const Color.fromARGB(255, 38, 34, 45),
|
||||||
|
surfaceContainerHigh: const Color.fromARGB(255, 52, 48, 62),
|
||||||
),
|
),
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
inputDecorationTheme: const InputDecorationTheme(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,6 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
||||||
contactSub?.cancel();
|
contactSub?.cancel();
|
||||||
groupActionsSub?.cancel();
|
groupActionsSub?.cancel();
|
||||||
_nextTypingIndicator?.cancel();
|
_nextTypingIndicator?.cancel();
|
||||||
textFieldFocus?.dispose();
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +107,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView>
|
||||||
});
|
});
|
||||||
|
|
||||||
protectMessageUpdating.protect(() async {
|
protectMessageUpdating.protect(() async {
|
||||||
if (groupActionsSub == null && !newGroup.isDirectChat) {
|
if (groupActionsSub == null) {
|
||||||
final actionsStream = twonlyDB.groupsDao.watchGroupActions(
|
final actionsStream = twonlyDB.groupsDao.watchGroupActions(
|
||||||
newGroup.groupId,
|
newGroup.groupId,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,24 @@ class _ChatGroupActionState extends State<ChatGroupAction> {
|
||||||
text = (contact == null)
|
text = (contact == null)
|
||||||
? context.lang.youLeftGroup
|
? context.lang.youLeftGroup
|
||||||
: context.lang.makerLeftGroup(maker);
|
: context.lang.makerLeftGroup(maker);
|
||||||
|
case GroupActionType.updatedContactUsername:
|
||||||
|
if (contact != null) {
|
||||||
|
icon = FontAwesomeIcons.userPen;
|
||||||
|
text = context.lang.makerChangedUsername(
|
||||||
|
maker,
|
||||||
|
widget.action.oldGroupName!,
|
||||||
|
widget.action.newGroupName!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case GroupActionType.updatedContactDisplayName:
|
||||||
|
if (contact != null) {
|
||||||
|
icon = FontAwesomeIcons.userPen;
|
||||||
|
text = context.lang.makerChangedDisplayName(
|
||||||
|
maker,
|
||||||
|
widget.action.oldGroupName!,
|
||||||
|
widget.action.newGroupName!,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// switch (widget.action.type) {
|
// switch (widget.action.type) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
@ -180,9 +180,31 @@ class _ContactViewState extends State<ContactView> {
|
||||||
body: ListView(
|
body: ListView(
|
||||||
key: ValueKey(contact.userId),
|
key: ValueKey(contact.userId),
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Center(
|
||||||
padding: const EdgeInsets.all(10),
|
child: GestureDetector(
|
||||||
child: AvatarIcon(contactId: contact.userId, fontSize: 30),
|
onTap: () {
|
||||||
|
// ignore: inference_failure_on_function_invocation
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AvatarIcon(contactId: contact.userId, fontSize: 200),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: AvatarIcon(contactId: contact.userId, fontSize: 30),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
@ -331,7 +353,9 @@ class _ContactViewState extends State<ContactView> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(context.lang.contactUserDiscoveryManualApprovalApprove),
|
child: Text(
|
||||||
|
context.lang.contactUserDiscoveryManualApprovalApprove,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,14 @@ extension SetupPagesExtension on SetupPages {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetupPages? previous() {
|
||||||
|
final prevIndex = index - 1;
|
||||||
|
if (prevIndex >= 0) {
|
||||||
|
return SetupPages.values[prevIndex];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SetupView extends StatefulWidget {
|
class SetupView extends StatefulWidget {
|
||||||
|
|
@ -119,31 +127,51 @@ class _SetupViewState extends State<SetupView> {
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
key: ValueKey(currentPage.name),
|
key: ValueKey(currentPage.name),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 30),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||||
children: [
|
children: [
|
||||||
_buildPage(currentPage, state),
|
_buildPage(currentPage, state),
|
||||||
if (!currentPage.isLast)
|
if (currentPage.index > 0 || !currentPage.isLast)
|
||||||
SizedBox(
|
Padding(
|
||||||
height: 50,
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: Center(
|
child: Row(
|
||||||
child: TextButton(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
onPressed: () async {
|
children: [
|
||||||
await UserService.update(
|
if (currentPage.index > 0)
|
||||||
(u) => u.skipSetupPages = true,
|
TextButton(
|
||||||
);
|
onPressed: () async {
|
||||||
widget.onUpdate?.call();
|
await UserService.update((u) {
|
||||||
},
|
u.currentSetupPage = currentPage.previous()?.name;
|
||||||
child: Text(
|
});
|
||||||
context.lang.onboardingFinishLater,
|
},
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
color: context.color.primary,
|
context.lang.back,
|
||||||
fontWeight: FontWeight.bold,
|
style: TextStyle(
|
||||||
|
color: context.color.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
if (currentPage.index > 0 && !currentPage.isLast)
|
||||||
),
|
const SizedBox(width: 24),
|
||||||
|
if (!currentPage.isLast)
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await UserService.update(
|
||||||
|
(u) => u.skipSetupPages = true,
|
||||||
|
);
|
||||||
|
widget.onUpdate?.call();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
context.lang.onboardingFinishLater,
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.color.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 60),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class AddNewContactsPage extends StatelessWidget {
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
padding: const EdgeInsets.symmetric(horizontal: 35),
|
||||||
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
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/src/constants/routes.keys.dart';
|
import 'package:twonly/src/constants/routes.keys.dart';
|
||||||
import 'package:twonly/src/services/backup/common.backup.dart';
|
import 'package:twonly/src/services/backup/common.backup.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
|
|
@ -24,19 +23,6 @@ class _BackupSetupPageState extends State<BackupSetupPage> {
|
||||||
final TextEditingController passwordCtrl = TextEditingController();
|
final TextEditingController passwordCtrl = TextEditingController();
|
||||||
final TextEditingController repeatedPasswordCtrl = TextEditingController();
|
final TextEditingController repeatedPasswordCtrl = TextEditingController();
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (userService.currentUser.twonlySafeBackup != null) {
|
|
||||||
// twonly safe is already configured...
|
|
||||||
UserService.update((user) {
|
|
||||||
user.currentSetupPage = SetupPages.backup.next()?.name;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> onPressedEnableTwonlySafe() async {
|
Future<bool> onPressedEnableTwonlySafe() async {
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
|
||||||
|
|
@ -8,53 +8,101 @@ class MockContactRequestActionsComp extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return IgnorePointer(
|
||||||
// width: 125,
|
child: SizedBox(
|
||||||
|
// width: 125,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
// width: 45,
|
||||||
|
child: FilledButton(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.only(right: 2, left: 4),
|
||||||
|
backgroundColor: context.color.surfaceContainerHigh,
|
||||||
|
foregroundColor: context.color.onSurface,
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.person_off_rounded,
|
||||||
|
color: Color.fromARGB(164, 244, 67, 54),
|
||||||
|
size: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
context.lang.contactActionBlock,
|
||||||
|
style: const TextStyle(fontSize: 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
// width: 50,
|
||||||
|
child: FilledButton(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.only(right: 2, left: 4),
|
||||||
|
backgroundColor: context.color.surfaceContainerHigh,
|
||||||
|
foregroundColor: context.color.onSurface,
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check, color: Colors.green, size: 12),
|
||||||
|
Text(
|
||||||
|
context.lang.contactActionAccept,
|
||||||
|
style: const TextStyle(fontSize: 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||||
|
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
|
),
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
icon: const Icon(Icons.close, size: 12),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockContactSuggestedActionsComp extends StatelessWidget {
|
||||||
|
const MockContactSuggestedActionsComp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IgnorePointer(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
// width: 45,
|
|
||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
padding: const EdgeInsets.only(right: 2, left: 4),
|
padding: const EdgeInsets.only(right: 8, left: 4),
|
||||||
backgroundColor: context.color.surfaceContainerHigh,
|
).merge(secondaryGreyButtonStyle(context)),
|
||||||
foregroundColor: context.color.onSurface,
|
|
||||||
),
|
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Padding(
|
||||||
Icons.person_off_rounded,
|
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||||
color: Color.fromARGB(164, 244, 67, 54),
|
child: FaIcon(FontAwesomeIcons.userPlus, size: 10),
|
||||||
size: 12,
|
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
context.lang.contactActionBlock,
|
context.lang.friendSuggestionsRequest,
|
||||||
style: const TextStyle(fontSize: 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
SizedBox(
|
|
||||||
height: 20,
|
|
||||||
// width: 50,
|
|
||||||
child: FilledButton(
|
|
||||||
style: FilledButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.only(right: 2, left: 4),
|
|
||||||
backgroundColor: context.color.surfaceContainerHigh,
|
|
||||||
foregroundColor: context.color.onSurface,
|
|
||||||
),
|
|
||||||
onPressed: () {},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.check, color: Colors.green, size: 12),
|
|
||||||
Text(
|
|
||||||
context.lang.contactActionAccept,
|
|
||||||
style: const TextStyle(fontSize: 8),
|
style: const TextStyle(fontSize: 8),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -75,47 +123,3 @@ class MockContactRequestActionsComp extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockContactSuggestedActionsComp extends StatelessWidget {
|
|
||||||
const MockContactSuggestedActionsComp({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
SizedBox(
|
|
||||||
height: 20,
|
|
||||||
child: FilledButton(
|
|
||||||
style: FilledButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.only(right: 8, left: 4),
|
|
||||||
).merge(secondaryGreyButtonStyle(context)),
|
|
||||||
onPressed: () {},
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
|
||||||
child: FaIcon(FontAwesomeIcons.userPlus, size: 10),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
context.lang.friendSuggestionsRequest,
|
|
||||||
style: const TextStyle(fontSize: 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
style: IconButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
|
||||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
||||||
),
|
|
||||||
constraints: const BoxConstraints(),
|
|
||||||
icon: const Icon(Icons.close, size: 12),
|
|
||||||
onPressed: () {},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class NextButtonComp extends StatelessWidget {
|
||||||
userService.currentUser.currentSetupPage,
|
userService.currentUser.currentSetupPage,
|
||||||
);
|
);
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
onPressed: canSubmit
|
onPressed: (canSubmit && !isLoading)
|
||||||
? () async {
|
? () async {
|
||||||
if (onPressed != null) {
|
if (onPressed != null) {
|
||||||
final error = await onPressed?.call();
|
final error = await onPressed?.call();
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.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';
|
||||||
|
|
||||||
|
|
@ -17,20 +15,7 @@ class LetYourFriendsFindYou extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LetYourFriendsFindYouState extends State<LetYourFriendsFindYou> {
|
class _LetYourFriendsFindYouState extends State<LetYourFriendsFindYou> {
|
||||||
@override
|
bool _isLoading = false;
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (userService.currentUser.isUserDiscoveryEnabled &&
|
|
||||||
userService.currentUser.userDiscoverySharePromotion) {
|
|
||||||
// feature is already configured...
|
|
||||||
UserService.update((user) {
|
|
||||||
user.currentSetupPage = SetupPages.letYourFriendsFindYou.next()?.name;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -77,8 +62,19 @@ class _LetYourFriendsFindYouState extends State<LetYourFriendsFindYou> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 50),
|
const SizedBox(height: 50),
|
||||||
NextButtonComp(
|
NextButtonComp(
|
||||||
|
isLoading: _isLoading,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
return !(await widget.state.initializeOrUpdate());
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
final result = await widget.state.initializeOrUpdate();
|
||||||
|
return !result;
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/locator.dart';
|
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup.view.dart';
|
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup/components/next_button.comp.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';
|
||||||
|
|
||||||
|
|
@ -16,20 +13,6 @@ class ShareYourFriendsSetupPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ShareYourFriendsSetupPageState extends State<ShareYourFriendsSetupPage> {
|
class _ShareYourFriendsSetupPageState extends State<ShareYourFriendsSetupPage> {
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
if (userService.currentUser.isUserDiscoveryEnabled) {
|
|
||||||
// feature is already configured...
|
|
||||||
UserService.update((user) {
|
|
||||||
user.currentSetupPage = SetupPages.shareYourFriends.next()?.name;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Center(
|
return Center(
|
||||||
|
|
@ -41,11 +24,7 @@ class _ShareYourFriendsSetupPageState extends State<ShareYourFriendsSetupPage> {
|
||||||
showOnlySpecificPage: UserDiscoveryPages.shareYourFriends,
|
showOnlySpecificPage: UserDiscoveryPages.shareYourFriends,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 60),
|
const SizedBox(height: 60),
|
||||||
const NextButtonComp(
|
const NextButtonComp(),
|
||||||
// onPressed: () async {
|
|
||||||
// return !(await widget.state.initializeOrUpdate());
|
|
||||||
// },
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/locator.dart';
|
import 'package:twonly/locator.dart';
|
||||||
import 'package:twonly/src/services/user.service.dart';
|
import 'package:twonly/src/services/user.service.dart';
|
||||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
import 'package:twonly/src/services/user_discovery.service.dart';
|
||||||
|
import 'package:twonly/src/utils/log.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/verification_badge.comp.dart';
|
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||||
|
|
@ -9,18 +10,18 @@ import 'package:twonly/src/visual/views/contact/add_new_contact_components/frien
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/components/mock_contact_request_actions.comp.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup/components/mock_contact_request_actions.comp.dart';
|
||||||
import 'package:twonly/src/visual/views/onboarding/setup/components/setup_switch_card.comp.dart';
|
import 'package:twonly/src/visual/views/onboarding/setup/components/setup_switch_card.comp.dart';
|
||||||
|
|
||||||
const exampleUsers = [
|
List<String> getExampleUsers(BuildContext context) => [
|
||||||
'james',
|
context.lang.exampleUserName1,
|
||||||
'mary',
|
context.lang.exampleUserName2,
|
||||||
'john',
|
context.lang.exampleUserName3,
|
||||||
'patricia',
|
context.lang.exampleUserName4,
|
||||||
'robert',
|
context.lang.exampleUserName5,
|
||||||
'jennifer',
|
context.lang.exampleUserName6,
|
||||||
'michael',
|
context.lang.exampleUserName7,
|
||||||
'linda',
|
context.lang.exampleUserName8,
|
||||||
'william',
|
context.lang.exampleUserName9,
|
||||||
'lena',
|
context.lang.exampleUserName10,
|
||||||
'david',
|
context.lang.exampleUserName11,
|
||||||
];
|
];
|
||||||
|
|
||||||
class UserDiscoverySetupState {
|
class UserDiscoverySetupState {
|
||||||
|
|
@ -52,21 +53,39 @@ class UserDiscoverySetupState {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> initializeOrUpdate() async {
|
Future<bool> initializeOrUpdate() async {
|
||||||
if (isUserDiscoveryEnabled) {
|
try {
|
||||||
await UserDiscoveryService.initializeOrUpdate(
|
Log.info('UserDiscoverySetupState: initializeOrUpdate started');
|
||||||
threshold: threshold,
|
var hasError = false;
|
||||||
sharePromotion: sharePromotion,
|
if (isUserDiscoveryEnabled) {
|
||||||
);
|
Log.info('UserDiscoverySetupState: initializing UserDiscoveryService');
|
||||||
|
try {
|
||||||
|
await UserDiscoveryService.initializeOrUpdate(
|
||||||
|
threshold: threshold,
|
||||||
|
sharePromotion: sharePromotion,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(
|
||||||
|
'UserDiscoverySetupState: UserDiscoveryService failed or timed out: $e',
|
||||||
|
);
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.info('UserDiscoverySetupState: updating UserService');
|
||||||
|
await UserService.update((u) {
|
||||||
|
u
|
||||||
|
..isUserDiscoveryEnabled = isUserDiscoveryEnabled
|
||||||
|
..requiredSendImages = requiredSendImages
|
||||||
|
..userDiscoveryRequiresManualApproval = isManualApprovalEnabled
|
||||||
|
..userDiscoveryInitializationError = hasError;
|
||||||
|
});
|
||||||
|
|
||||||
|
Log.info('UserDiscoverySetupState: initializeOrUpdate finished');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
Log.error('UserDiscoverySetupState: initializeOrUpdate failed: $e');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await UserService.update((u) {
|
|
||||||
u
|
|
||||||
..isUserDiscoveryEnabled = isUserDiscoveryEnabled
|
|
||||||
..requiredSendImages = requiredSendImages
|
|
||||||
..userDiscoveryRequiresManualApproval = isManualApprovalEnabled;
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,15 +126,12 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
SetupSwitchCard(
|
SetupSwitchCard(
|
||||||
value: state.isUserDiscoveryEnabled,
|
value: state.isUserDiscoveryEnabled,
|
||||||
onChanged: (val) => state.update(() {
|
onChanged: (val) => state.update(() {
|
||||||
state.isUserDiscoveryEnabled = val;
|
state.isUserDiscoveryEnabled = val;
|
||||||
if (!val) {
|
|
||||||
state.sharePromotion = false;
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
title: context.lang.onboardingUserDiscoveryShareFriends,
|
title: context.lang.onboardingUserDiscoveryShareFriends,
|
||||||
expandedChild: Column(
|
expandedChild: Column(
|
||||||
|
|
@ -143,48 +159,10 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 8),
|
padding: EdgeInsets.only(bottom: 8),
|
||||||
child: Divider(),
|
child: Divider(),
|
||||||
),
|
),
|
||||||
Text(
|
const _ExampleLabel(),
|
||||||
context.lang.onboardingUserDiscoveryContactsVerifiedBadge,
|
|
||||||
style: TextStyle(
|
|
||||||
color: context.color.onSurfaceVariant,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Center(
|
|
||||||
child: Container(
|
|
||||||
width: 100,
|
|
||||||
height: 40,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(color: Colors.grey, width: 0.5),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: const Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
AvatarIcon(fontSize: 12),
|
|
||||||
SizedBox(width: 5),
|
|
||||||
Text(
|
|
||||||
'jane',
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 5),
|
|
||||||
VerificationBadgeComp(
|
|
||||||
isVerifiedByTransferredTrust: true,
|
|
||||||
size: 14,
|
|
||||||
clickable: false,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
Text(
|
Text(
|
||||||
context.lang.onboardingUserDiscoveryWhoIsRequesting,
|
context.lang.onboardingUserDiscoveryWhoIsRequesting,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -214,9 +192,9 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
Text(
|
||||||
'jane',
|
context.lang.exampleJane,
|
||||||
style: TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
),
|
),
|
||||||
|
|
@ -226,8 +204,8 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
||||||
children: buildFriendsListTextString(
|
children: buildFriendsListTextString(
|
||||||
context,
|
context,
|
||||||
[
|
[
|
||||||
'mary',
|
context.lang.exampleUserName2,
|
||||||
'james',
|
context.lang.exampleUserName1,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
style: const TextStyle(fontSize: 10),
|
style: const TextStyle(fontSize: 10),
|
||||||
|
|
@ -241,6 +219,47 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Text(
|
||||||
|
context.lang.onboardingUserDiscoveryContactsVerifiedBadge,
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.color.onSurfaceVariant,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
width: 100,
|
||||||
|
height: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey, width: 0.5),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const AvatarIcon(fontSize: 12),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(
|
||||||
|
context.lang.exampleJane,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
const VerificationBadgeComp(
|
||||||
|
isVerifiedByTransferredTrust: true,
|
||||||
|
size: 14,
|
||||||
|
clickable: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -275,18 +294,15 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
||||||
SetupSwitchCard(
|
SetupSwitchCard(
|
||||||
value: state.sharePromotion,
|
value: state.sharePromotion,
|
||||||
onChanged: (val) => state.update(() {
|
onChanged: (val) => state.update(() {
|
||||||
if (val) {
|
|
||||||
state.isUserDiscoveryEnabled = true;
|
|
||||||
}
|
|
||||||
state.sharePromotion = val;
|
state.sharePromotion = val;
|
||||||
}),
|
}),
|
||||||
title: context.lang.onboardingUserDiscoveryBeRecommended,
|
title: context.lang.onboardingUserDiscoveryBeRecommended,
|
||||||
expandedChild: Padding(
|
expandedChild: Column(
|
||||||
padding: const EdgeInsets.all(12),
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -319,121 +335,128 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Text(
|
const Padding(
|
||||||
context.lang.onboardingUserDiscoveryWhatOthersSee,
|
padding: EdgeInsets.only(bottom: 8, top: 8),
|
||||||
style: TextStyle(
|
child: Divider(),
|
||||||
color: context.color.onSurfaceVariant,
|
),
|
||||||
fontSize: 12,
|
const _ExampleLabel(),
|
||||||
),
|
Text(
|
||||||
textAlign: TextAlign.center,
|
context.lang.onboardingUserDiscoveryWhatOthersSee,
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.color.onSurfaceVariant,
|
||||||
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
textAlign: TextAlign.center,
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
const SizedBox(height: 16),
|
||||||
child: Container(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
horizontal: 6,
|
child: Container(
|
||||||
vertical: 3,
|
padding: const EdgeInsets.symmetric(
|
||||||
),
|
horizontal: 6,
|
||||||
decoration: BoxDecoration(
|
vertical: 3,
|
||||||
border: Border.all(color: Colors.grey, width: 0.5),
|
),
|
||||||
borderRadius: BorderRadius.circular(12),
|
decoration: BoxDecoration(
|
||||||
),
|
border: Border.all(color: Colors.grey, width: 0.5),
|
||||||
child: Row(
|
borderRadius: BorderRadius.circular(12),
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
),
|
||||||
children: [
|
child: Row(
|
||||||
const AvatarIcon(fontSize: 14),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
const SizedBox(width: 5),
|
children: [
|
||||||
Expanded(
|
const AvatarIcon(fontSize: 14),
|
||||||
child: Column(
|
const SizedBox(width: 5),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Expanded(
|
||||||
children: [
|
child: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
userService.currentUser.username,
|
children: [
|
||||||
style: const TextStyle(
|
Text(
|
||||||
fontWeight: FontWeight.bold,
|
userService.currentUser.username,
|
||||||
),
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
RichText(
|
),
|
||||||
text: TextSpan(
|
RichText(
|
||||||
children: buildFriendsListTextString(
|
text: TextSpan(
|
||||||
context,
|
children: buildFriendsListTextString(
|
||||||
exampleUsers.sublist(
|
context,
|
||||||
0,
|
getExampleUsers(context).sublist(
|
||||||
state.threshold,
|
0,
|
||||||
),
|
state.threshold,
|
||||||
),
|
),
|
||||||
style: const TextStyle(fontSize: 11),
|
|
||||||
),
|
),
|
||||||
|
style: const TextStyle(fontSize: 11),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
const MockContactSuggestedActionsComp(),
|
),
|
||||||
],
|
const MockContactSuggestedActionsComp(),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
Text(
|
const SizedBox(height: 16),
|
||||||
context.lang.onboardingUserDiscoveryWhatYouSee,
|
Text(
|
||||||
style: TextStyle(
|
context.lang.onboardingUserDiscoveryWhatYouSee,
|
||||||
color: context.color.onSurfaceVariant,
|
style: TextStyle(
|
||||||
fontSize: 12,
|
color: context.color.onSurfaceVariant,
|
||||||
),
|
fontSize: 12,
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
textAlign: TextAlign.center,
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
const SizedBox(height: 16),
|
||||||
child: Container(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 6,
|
horizontal: 16,
|
||||||
vertical: 3,
|
),
|
||||||
),
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
padding: const EdgeInsets.symmetric(
|
||||||
border: Border.all(color: Colors.grey, width: 0.5),
|
horizontal: 6,
|
||||||
borderRadius: BorderRadius.circular(12),
|
vertical: 3,
|
||||||
),
|
),
|
||||||
child: Row(
|
decoration: BoxDecoration(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
border: Border.all(color: Colors.grey, width: 0.5),
|
||||||
children: [
|
borderRadius: BorderRadius.circular(12),
|
||||||
const AvatarIcon(fontSize: 14),
|
),
|
||||||
const SizedBox(width: 5),
|
child: Row(
|
||||||
Expanded(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
const AvatarIcon(fontSize: 14),
|
||||||
children: [
|
const SizedBox(width: 5),
|
||||||
const Text(
|
Expanded(
|
||||||
'jane',
|
child: Column(
|
||||||
style: TextStyle(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
fontWeight: FontWeight.bold,
|
children: [
|
||||||
fontSize: 13,
|
Text(
|
||||||
),
|
context.lang.exampleJane,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 13,
|
||||||
),
|
),
|
||||||
RichText(
|
),
|
||||||
text: TextSpan(
|
RichText(
|
||||||
children: buildFriendsListTextString(
|
text: TextSpan(
|
||||||
context,
|
children: buildFriendsListTextString(
|
||||||
exampleUsers.sublist(
|
context,
|
||||||
0,
|
getExampleUsers(context).sublist(
|
||||||
state.threshold,
|
0,
|
||||||
),
|
state.threshold,
|
||||||
),
|
),
|
||||||
style: const TextStyle(fontSize: 10),
|
|
||||||
),
|
),
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
const MockContactRequestActionsComp(),
|
),
|
||||||
],
|
const MockContactRequestActionsComp(),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -442,3 +465,28 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ExampleLabel extends StatelessWidget {
|
||||||
|
const _ExampleLabel();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 12),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey, width: 0.5),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
context.lang.onboardingExampleLabel,
|
||||||
|
style: const TextStyle(fontSize: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ description: "twonly, a privacy-friendly way to connect with friends through sec
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 0.2.8+117
|
version: 0.2.9+118
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.0
|
sdk: ^3.11.0
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ impl UserDiscoveryUtils for UserDiscoveryUtilsFlutter {
|
||||||
|
|
||||||
impl UserDiscoveryStore for UserDiscoveryStoreFlutter {
|
impl UserDiscoveryStore for UserDiscoveryStoreFlutter {
|
||||||
async fn get_config(&self) -> Result<String> {
|
async fn get_config(&self) -> Result<String> {
|
||||||
let ws = get_twonly_flutter().unwrap();
|
let ws = get_twonly_flutter()?;
|
||||||
let config_path =
|
let config_path =
|
||||||
PathBuf::from(&ws.config.data_directory).join("user_discovery_config.json");
|
PathBuf::from(&ws.config.data_directory).join("user_discovery_config.json");
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ impl UserDiscoveryStore for UserDiscoveryStoreFlutter {
|
||||||
|
|
||||||
async fn update_config(&self, update: String) -> Result<()> {
|
async fn update_config(&self, update: String) -> Result<()> {
|
||||||
tracing::debug!("Updating configuration file.");
|
tracing::debug!("Updating configuration file.");
|
||||||
let ws = get_twonly_flutter().unwrap();
|
let ws = get_twonly_flutter()?;
|
||||||
let config_path =
|
let config_path =
|
||||||
PathBuf::from(&ws.config.data_directory).join("user_discovery_config.json");
|
PathBuf::from(&ws.config.data_directory).join("user_discovery_config.json");
|
||||||
std::fs::write(config_path, &update)?;
|
std::fs::write(config_path, &update)?;
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,16 @@ impl FlutterUserDiscovery {
|
||||||
public_key: Vec<u8>,
|
public_key: Vec<u8>,
|
||||||
share_promotion: bool,
|
share_promotion: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
Ok(get_twonly_flutter()?
|
tracing::info!("Rust bridge: initialize_or_update started");
|
||||||
.user_discovery
|
let twonly = get_twonly_flutter()?;
|
||||||
.get()
|
tracing::info!("Rust bridge: getting user_discovery lock");
|
||||||
.await
|
let user_discovery = twonly.user_discovery.get().await;
|
||||||
|
tracing::info!("Rust bridge: calling initialize_or_update on protocols");
|
||||||
|
let res = user_discovery
|
||||||
.initialize_or_update(threshold, user_id, public_key, share_promotion)
|
.initialize_or_update(threshold, user_id, public_key, share_promotion)
|
||||||
.await?)
|
.await;
|
||||||
|
tracing::info!("Rust bridge: initialize_or_update on protocols finished");
|
||||||
|
Ok(res?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_current_version() -> Result<Vec<u8>> {
|
pub async fn get_current_version() -> Result<Vec<u8>> {
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
public_key: Vec<u8>,
|
public_key: Vec<u8>,
|
||||||
share_promotion: bool,
|
share_promotion: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let config_lock = self.config_lock.lock().await;
|
tracing::info!("Protocols: initialize_or_update started, getting config from store");
|
||||||
let mut config = match self.store.get_config().await {
|
let config = match self.store.get_config().await {
|
||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
let mut config: UserDiscoveryConfig = serde_json::from_str(&config)?;
|
let mut config: UserDiscoveryConfig = serde_json::from_str(&config)?;
|
||||||
config.threshold = threshold;
|
config.threshold = threshold;
|
||||||
|
|
@ -113,23 +113,39 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
||||||
public_key,
|
public_key,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tracing::info!("Protocols: signing data");
|
||||||
let signature = self.utils.sign_data(&signed_data.encode_to_vec()).await?;
|
let signature = self.utils.sign_data(&signed_data.encode_to_vec()).await?;
|
||||||
|
|
||||||
debug_assert_eq!(threshold, config.threshold);
|
debug_assert_eq!(threshold, config.threshold);
|
||||||
|
|
||||||
|
tracing::info!("Protocols: setting up announcements");
|
||||||
let verification_shares = self
|
let verification_shares = self
|
||||||
.setup_announcements(&config, signed_data, signature)
|
.setup_announcements(&config, signed_data, signature)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
debug_assert_eq!(verification_shares.len(), threshold as usize - 1);
|
debug_assert_eq!(verification_shares.len(), threshold as usize - 1);
|
||||||
|
|
||||||
config.public_id = public_id;
|
tracing::info!("Protocols: updating config in store");
|
||||||
config.announcement_version += 1;
|
|
||||||
config.verification_shares = verification_shares;
|
|
||||||
config.share_promotion = share_promotion;
|
|
||||||
|
|
||||||
self.update_config(config, config_lock).await?;
|
let config_lock = self.config_lock.lock().await;
|
||||||
|
let mut final_config = match self.store.get_config().await {
|
||||||
|
Ok(c) => serde_json::from_str(&c)?,
|
||||||
|
Err(_) => UserDiscoveryConfig {
|
||||||
|
threshold,
|
||||||
|
user_id,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
final_config.public_id = public_id;
|
||||||
|
final_config.announcement_version += 1;
|
||||||
|
final_config.verification_shares = verification_shares;
|
||||||
|
final_config.share_promotion = share_promotion;
|
||||||
|
final_config.threshold = threshold;
|
||||||
|
|
||||||
|
self.update_config(final_config, config_lock).await?;
|
||||||
|
|
||||||
|
tracing::info!("Protocols: initialize_or_update finished");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue