mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 02:12:13 +00:00
implement manual approval and fix bug
This commit is contained in:
parent
f0741bfdc1
commit
c54265495c
22 changed files with 356 additions and 110 deletions
|
|
@ -3,7 +3,7 @@
|
|||
## 0.1.9
|
||||
|
||||
- New: Feature to find friends without a phone number
|
||||
- New: The verification state is now transferred to the scanned user.
|
||||
- New: The verification state is now transferred to the scanned user
|
||||
- New: Registration setup to configure the most important configurations
|
||||
- Improved: FAQ is now in the app rather than opening in the browser
|
||||
- Fix: Many smaller issues
|
||||
|
|
|
|||
|
|
@ -135,27 +135,35 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
|||
}
|
||||
|
||||
Stream<List<Contact>> watchContactsAnnouncedViaUserDiscovery() {
|
||||
return (select(contacts)..where(
|
||||
(t) =>
|
||||
t.userDiscoveryVersion.isNotNull() &
|
||||
t.userDiscoveryExcluded.equals(false) &
|
||||
t.mediaSendCounter.isBiggerOrEqualValue(
|
||||
userService.currentUser.requiredSendImages,
|
||||
),
|
||||
))
|
||||
.watch();
|
||||
return (select(contacts)..where((t) {
|
||||
var expr = t.userDiscoveryVersion.isNotNull() &
|
||||
t.userDiscoveryExcluded.equals(false) &
|
||||
t.mediaSendCounter.isBiggerOrEqualValue(
|
||||
userService.currentUser.requiredSendImages,
|
||||
);
|
||||
|
||||
if (userService.currentUser.userDiscoveryRequiresManualApproval) {
|
||||
expr = expr & t.userDiscoveryManualApproved.equals(true);
|
||||
}
|
||||
|
||||
return expr;
|
||||
})).watch();
|
||||
}
|
||||
|
||||
Future<List<Contact>> getContactsAnnouncedViaUserDiscovery() async {
|
||||
return (select(contacts)..where(
|
||||
(t) =>
|
||||
t.userDiscoveryVersion.isNotNull() &
|
||||
t.userDiscoveryExcluded.equals(false) &
|
||||
t.mediaSendCounter.isBiggerOrEqualValue(
|
||||
userService.currentUser.requiredSendImages,
|
||||
),
|
||||
))
|
||||
.get();
|
||||
return (select(contacts)..where((t) {
|
||||
var expr = t.userDiscoveryVersion.isNotNull() &
|
||||
t.userDiscoveryExcluded.equals(false) &
|
||||
t.mediaSendCounter.isBiggerOrEqualValue(
|
||||
userService.currentUser.requiredSendImages,
|
||||
);
|
||||
|
||||
if (userService.currentUser.userDiscoveryRequiresManualApproval) {
|
||||
expr = expr & t.userDiscoveryManualApproved.equals(true);
|
||||
}
|
||||
|
||||
return expr;
|
||||
})).get();
|
||||
}
|
||||
|
||||
Stream<List<Contact>> watchAllContacts() {
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@
|
|||
"name": "user_discovery_manual_approved",
|
||||
"getter_name": "userDiscoveryManualApproved",
|
||||
"moor_type": "bool",
|
||||
"nullable": false,
|
||||
"nullable": true,
|
||||
"customConstraints": null,
|
||||
"defaultConstraints": "CHECK (\"user_discovery_manual_approved\" IN (0, 1))",
|
||||
"dialectAwareDefaultConstraints": {
|
||||
|
|
@ -2557,7 +2557,7 @@
|
|||
"sql": [
|
||||
{
|
||||
"dialect": "sqlite",
|
||||
"sql": "CREATE TABLE IF NOT EXISTS \"contacts\" (\"user_id\" INTEGER NOT NULL, \"username\" TEXT NOT NULL, \"display_name\" TEXT NULL, \"nick_name\" TEXT NULL, \"avatar_svg_compressed\" BLOB NULL, \"sender_profile_counter\" INTEGER NOT NULL DEFAULT 0, \"accepted\" INTEGER NOT NULL DEFAULT 0 CHECK (\"accepted\" IN (0, 1)), \"deleted_by_user\" INTEGER NOT NULL DEFAULT 0 CHECK (\"deleted_by_user\" IN (0, 1)), \"requested\" INTEGER NOT NULL DEFAULT 0 CHECK (\"requested\" IN (0, 1)), \"blocked\" INTEGER NOT NULL DEFAULT 0 CHECK (\"blocked\" IN (0, 1)), \"verified\" INTEGER NOT NULL DEFAULT 0 CHECK (\"verified\" IN (0, 1)), \"account_deleted\" INTEGER NOT NULL DEFAULT 0 CHECK (\"account_deleted\" IN (0, 1)), \"created_at\" INTEGER NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)), \"user_discovery_version\" BLOB NULL, \"user_discovery_excluded\" INTEGER NOT NULL DEFAULT 0 CHECK (\"user_discovery_excluded\" IN (0, 1)), \"user_discovery_manual_approved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"user_discovery_manual_approved\" IN (0, 1)), \"media_send_counter\" INTEGER NOT NULL DEFAULT 0, \"media_received_counter\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"user_id\"));"
|
||||
"sql": "CREATE TABLE IF NOT EXISTS \"contacts\" (\"user_id\" INTEGER NOT NULL, \"username\" TEXT NOT NULL, \"display_name\" TEXT NULL, \"nick_name\" TEXT NULL, \"avatar_svg_compressed\" BLOB NULL, \"sender_profile_counter\" INTEGER NOT NULL DEFAULT 0, \"accepted\" INTEGER NOT NULL DEFAULT 0 CHECK (\"accepted\" IN (0, 1)), \"deleted_by_user\" INTEGER NOT NULL DEFAULT 0 CHECK (\"deleted_by_user\" IN (0, 1)), \"requested\" INTEGER NOT NULL DEFAULT 0 CHECK (\"requested\" IN (0, 1)), \"blocked\" INTEGER NOT NULL DEFAULT 0 CHECK (\"blocked\" IN (0, 1)), \"verified\" INTEGER NOT NULL DEFAULT 0 CHECK (\"verified\" IN (0, 1)), \"account_deleted\" INTEGER NOT NULL DEFAULT 0 CHECK (\"account_deleted\" IN (0, 1)), \"created_at\" INTEGER NOT NULL DEFAULT (CAST(strftime('%s', CURRENT_TIMESTAMP) AS INTEGER)), \"user_discovery_version\" BLOB NULL, \"user_discovery_excluded\" INTEGER NOT NULL DEFAULT 0 CHECK (\"user_discovery_excluded\" IN (0, 1)), \"user_discovery_manual_approved\" INTEGER NULL DEFAULT 0 CHECK (\"user_discovery_manual_approved\" IN (0, 1)), \"media_send_counter\" INTEGER NOT NULL DEFAULT 0, \"media_received_counter\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"user_id\"));"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class Contacts extends Table {
|
|||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
BoolColumn get userDiscoveryManualApproved =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
boolean().nullable().withDefault(const Constant(false))();
|
||||
|
||||
IntColumn get mediaSendCounter => integer().withDefault(const Constant(0))();
|
||||
IntColumn get mediaReceivedCounter =>
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
|
|||
GeneratedColumn<bool>(
|
||||
'user_discovery_manual_approved',
|
||||
aliasedName,
|
||||
false,
|
||||
true,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
|
|
@ -483,7 +483,7 @@ class $ContactsTable extends Contacts with TableInfo<$ContactsTable, Contact> {
|
|||
userDiscoveryManualApproved: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.bool,
|
||||
data['${effectivePrefix}user_discovery_manual_approved'],
|
||||
)!,
|
||||
),
|
||||
mediaSendCounter: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}media_send_counter'],
|
||||
|
|
@ -517,7 +517,7 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
final DateTime createdAt;
|
||||
final Uint8List? userDiscoveryVersion;
|
||||
final bool userDiscoveryExcluded;
|
||||
final bool userDiscoveryManualApproved;
|
||||
final bool? userDiscoveryManualApproved;
|
||||
final int mediaSendCounter;
|
||||
final int mediaReceivedCounter;
|
||||
const Contact({
|
||||
|
|
@ -536,7 +536,7 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
required this.createdAt,
|
||||
this.userDiscoveryVersion,
|
||||
required this.userDiscoveryExcluded,
|
||||
required this.userDiscoveryManualApproved,
|
||||
this.userDiscoveryManualApproved,
|
||||
required this.mediaSendCounter,
|
||||
required this.mediaReceivedCounter,
|
||||
});
|
||||
|
|
@ -566,9 +566,11 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
map['user_discovery_version'] = Variable<Uint8List>(userDiscoveryVersion);
|
||||
}
|
||||
map['user_discovery_excluded'] = Variable<bool>(userDiscoveryExcluded);
|
||||
map['user_discovery_manual_approved'] = Variable<bool>(
|
||||
userDiscoveryManualApproved,
|
||||
);
|
||||
if (!nullToAbsent || userDiscoveryManualApproved != null) {
|
||||
map['user_discovery_manual_approved'] = Variable<bool>(
|
||||
userDiscoveryManualApproved,
|
||||
);
|
||||
}
|
||||
map['media_send_counter'] = Variable<int>(mediaSendCounter);
|
||||
map['media_received_counter'] = Variable<int>(mediaReceivedCounter);
|
||||
return map;
|
||||
|
|
@ -599,7 +601,10 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
? const Value.absent()
|
||||
: Value(userDiscoveryVersion),
|
||||
userDiscoveryExcluded: Value(userDiscoveryExcluded),
|
||||
userDiscoveryManualApproved: Value(userDiscoveryManualApproved),
|
||||
userDiscoveryManualApproved:
|
||||
userDiscoveryManualApproved == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(userDiscoveryManualApproved),
|
||||
mediaSendCounter: Value(mediaSendCounter),
|
||||
mediaReceivedCounter: Value(mediaReceivedCounter),
|
||||
);
|
||||
|
|
@ -634,7 +639,7 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
userDiscoveryExcluded: serializer.fromJson<bool>(
|
||||
json['userDiscoveryExcluded'],
|
||||
),
|
||||
userDiscoveryManualApproved: serializer.fromJson<bool>(
|
||||
userDiscoveryManualApproved: serializer.fromJson<bool?>(
|
||||
json['userDiscoveryManualApproved'],
|
||||
),
|
||||
mediaSendCounter: serializer.fromJson<int>(json['mediaSendCounter']),
|
||||
|
|
@ -664,7 +669,7 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
userDiscoveryVersion,
|
||||
),
|
||||
'userDiscoveryExcluded': serializer.toJson<bool>(userDiscoveryExcluded),
|
||||
'userDiscoveryManualApproved': serializer.toJson<bool>(
|
||||
'userDiscoveryManualApproved': serializer.toJson<bool?>(
|
||||
userDiscoveryManualApproved,
|
||||
),
|
||||
'mediaSendCounter': serializer.toJson<int>(mediaSendCounter),
|
||||
|
|
@ -688,7 +693,7 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
DateTime? createdAt,
|
||||
Value<Uint8List?> userDiscoveryVersion = const Value.absent(),
|
||||
bool? userDiscoveryExcluded,
|
||||
bool? userDiscoveryManualApproved,
|
||||
Value<bool?> userDiscoveryManualApproved = const Value.absent(),
|
||||
int? mediaSendCounter,
|
||||
int? mediaReceivedCounter,
|
||||
}) => Contact(
|
||||
|
|
@ -711,8 +716,9 @@ class Contact extends DataClass implements Insertable<Contact> {
|
|||
? userDiscoveryVersion.value
|
||||
: this.userDiscoveryVersion,
|
||||
userDiscoveryExcluded: userDiscoveryExcluded ?? this.userDiscoveryExcluded,
|
||||
userDiscoveryManualApproved:
|
||||
userDiscoveryManualApproved ?? this.userDiscoveryManualApproved,
|
||||
userDiscoveryManualApproved: userDiscoveryManualApproved.present
|
||||
? userDiscoveryManualApproved.value
|
||||
: this.userDiscoveryManualApproved,
|
||||
mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter,
|
||||
mediaReceivedCounter: mediaReceivedCounter ?? this.mediaReceivedCounter,
|
||||
);
|
||||
|
|
@ -852,7 +858,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
final Value<DateTime> createdAt;
|
||||
final Value<Uint8List?> userDiscoveryVersion;
|
||||
final Value<bool> userDiscoveryExcluded;
|
||||
final Value<bool> userDiscoveryManualApproved;
|
||||
final Value<bool?> userDiscoveryManualApproved;
|
||||
final Value<int> mediaSendCounter;
|
||||
final Value<int> mediaReceivedCounter;
|
||||
const ContactsCompanion({
|
||||
|
|
@ -959,7 +965,7 @@ class ContactsCompanion extends UpdateCompanion<Contact> {
|
|||
Value<DateTime>? createdAt,
|
||||
Value<Uint8List?>? userDiscoveryVersion,
|
||||
Value<bool>? userDiscoveryExcluded,
|
||||
Value<bool>? userDiscoveryManualApproved,
|
||||
Value<bool?>? userDiscoveryManualApproved,
|
||||
Value<int>? mediaSendCounter,
|
||||
Value<int>? mediaReceivedCounter,
|
||||
}) {
|
||||
|
|
@ -11687,7 +11693,7 @@ typedef $$ContactsTableCreateCompanionBuilder =
|
|||
Value<DateTime> createdAt,
|
||||
Value<Uint8List?> userDiscoveryVersion,
|
||||
Value<bool> userDiscoveryExcluded,
|
||||
Value<bool> userDiscoveryManualApproved,
|
||||
Value<bool?> userDiscoveryManualApproved,
|
||||
Value<int> mediaSendCounter,
|
||||
Value<int> mediaReceivedCounter,
|
||||
});
|
||||
|
|
@ -11708,7 +11714,7 @@ typedef $$ContactsTableUpdateCompanionBuilder =
|
|||
Value<DateTime> createdAt,
|
||||
Value<Uint8List?> userDiscoveryVersion,
|
||||
Value<bool> userDiscoveryExcluded,
|
||||
Value<bool> userDiscoveryManualApproved,
|
||||
Value<bool?> userDiscoveryManualApproved,
|
||||
Value<int> mediaSendCounter,
|
||||
Value<int> mediaReceivedCounter,
|
||||
});
|
||||
|
|
@ -12967,7 +12973,7 @@ class $$ContactsTableTableManager
|
|||
Value<DateTime> createdAt = const Value.absent(),
|
||||
Value<Uint8List?> userDiscoveryVersion = const Value.absent(),
|
||||
Value<bool> userDiscoveryExcluded = const Value.absent(),
|
||||
Value<bool> userDiscoveryManualApproved = const Value.absent(),
|
||||
Value<bool?> userDiscoveryManualApproved = const Value.absent(),
|
||||
Value<int> mediaSendCounter = const Value.absent(),
|
||||
Value<int> mediaReceivedCounter = const Value.absent(),
|
||||
}) => ContactsCompanion(
|
||||
|
|
@ -13007,7 +13013,7 @@ class $$ContactsTableTableManager
|
|||
Value<DateTime> createdAt = const Value.absent(),
|
||||
Value<Uint8List?> userDiscoveryVersion = const Value.absent(),
|
||||
Value<bool> userDiscoveryExcluded = const Value.absent(),
|
||||
Value<bool> userDiscoveryManualApproved = const Value.absent(),
|
||||
Value<bool?> userDiscoveryManualApproved = const Value.absent(),
|
||||
Value<int> mediaSendCounter = const Value.absent(),
|
||||
Value<int> mediaReceivedCounter = const Value.absent(),
|
||||
}) => ContactsCompanion.insert(
|
||||
|
|
|
|||
|
|
@ -6294,10 +6294,10 @@ i1.GeneratedColumn<int> _column_213(String aliasedName) =>
|
|||
i1.GeneratedColumn<int>(
|
||||
'user_discovery_manual_approved',
|
||||
aliasedName,
|
||||
false,
|
||||
true,
|
||||
type: i1.DriftSqlType.int,
|
||||
$customConstraints:
|
||||
'NOT NULL DEFAULT 0 CHECK (user_discovery_manual_approved IN (0, 1))',
|
||||
'NULL DEFAULT 0 CHECK (user_discovery_manual_approved IN (0, 1))',
|
||||
defaultValue: const i1.CustomExpression('0'),
|
||||
);
|
||||
i1.GeneratedColumn<int> _column_214(String aliasedName) =>
|
||||
|
|
|
|||
|
|
@ -2971,6 +2971,24 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Stop sharing'**
|
||||
String get userDiscoveryEnabledStopSharing;
|
||||
|
||||
/// No description provided for @userDiscoveryManualApprovalReachedThreshold.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{username} has reached your threshold and now needs your manual approval to be shared with your friends.'**
|
||||
String userDiscoveryManualApprovalReachedThreshold(Object username);
|
||||
|
||||
/// No description provided for @userDiscoveryManualApprovalHideContact.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hide contact'**
|
||||
String get userDiscoveryManualApprovalHideContact;
|
||||
|
||||
/// No description provided for @userDiscoveryManualApprovalShareContact.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Share contact'**
|
||||
String get userDiscoveryManualApprovalShareContact;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -1672,4 +1672,15 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get userDiscoveryEnabledStopSharing => 'Nicht mehr teilen';
|
||||
|
||||
@override
|
||||
String userDiscoveryManualApprovalReachedThreshold(Object username) {
|
||||
return '$username hat deinen Schwellenwert erreicht und benötigt nur noch eine manuelle Zustimmung, um mit deinen Freunden geteilt zu werden.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get userDiscoveryManualApprovalHideContact => 'Kontakt verbergen';
|
||||
|
||||
@override
|
||||
String get userDiscoveryManualApprovalShareContact => 'Kontakt teilen';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1656,4 +1656,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get userDiscoveryEnabledStopSharing => 'Stop sharing';
|
||||
|
||||
@override
|
||||
String userDiscoveryManualApprovalReachedThreshold(Object username) {
|
||||
return '$username has reached your threshold and now needs your manual approval to be shared with your friends.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get userDiscoveryManualApprovalHideContact => 'Hide contact';
|
||||
|
||||
@override
|
||||
String get userDiscoveryManualApprovalShareContact => 'Share contact';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit dc33fa1bb1e0b9f6d15a0ee36ed37a7f0063f2a0
|
||||
Subproject commit 492bef11cf6bd472a71bd1c1b3007d731adea433
|
||||
|
|
@ -24,6 +24,7 @@ import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
|||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pbserver.dart';
|
||||
import 'package:twonly/src/services/api/client2client/user_discovery.c2c.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/download.api.dart';
|
||||
import 'package:twonly/src/services/api/mediafiles/upload.api.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
|
|
@ -123,6 +124,7 @@ class ApiService {
|
|||
unawaited(setupNotificationWithUsers());
|
||||
unawaited(signalHandleNewServerConnection());
|
||||
resetResyncedUsers();
|
||||
resetUserDiscoveryRequestUpdates();
|
||||
unawaited(fetchGroupStatesForUnjoinedGroups());
|
||||
unawaited(fetchMissingGroupPublicKey());
|
||||
unawaited(checkForDeletedUsernames());
|
||||
|
|
|
|||
|
|
@ -185,7 +185,6 @@ Future<int?> checkForProfileUpdate(
|
|||
.getSingleOrNull();
|
||||
if (contact != null) {
|
||||
if (contact.senderProfileCounter < senderProfileCounter) {
|
||||
Log.info('${contact.senderProfileCounter} < $senderProfileCounter');
|
||||
await sendCipherText(
|
||||
fromUserId,
|
||||
EncryptedContent(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@ import 'package:twonly/src/utils/log.dart';
|
|||
|
||||
final _requestedUpdates = <int>{};
|
||||
|
||||
void resetUserDiscoveryRequestUpdates() {
|
||||
_requestedUpdates.clear();
|
||||
}
|
||||
|
||||
Future<void> checkForUserDiscoveryChanges(
|
||||
int fromUserId,
|
||||
List<int> receivedVersion,
|
||||
|
|
@ -19,7 +23,7 @@ Future<void> checkForUserDiscoveryChanges(
|
|||
|
||||
if (currentVersion != null) {
|
||||
if (_requestedUpdates.contains(fromUserId)) {
|
||||
/// Only request a new version once per app session
|
||||
// Only request a new version once per app session
|
||||
return;
|
||||
}
|
||||
Log.info('Having old version from contact. Requesting new version.');
|
||||
|
|
@ -46,12 +50,10 @@ Future<void> handleUserDiscoveryRequest(
|
|||
return;
|
||||
}
|
||||
final contact = await twonlyDB.contactsDao.getContactById(fromUserId);
|
||||
if (contact == null) return;
|
||||
|
||||
if (contact.mediaSendCounter < userService.currentUser.requiredSendImages ||
|
||||
contact.userDiscoveryExcluded) {
|
||||
if (!UserDiscoveryService.isContactAllowed(contact)) {
|
||||
Log.warn(
|
||||
'Got a request to update user discovery, but mediaSendCounter (${contact.mediaSendCounter}) < ${userService.currentUser.requiredSendImages} or user is excluded ${contact.userDiscoveryExcluded}',
|
||||
'Got a request to update user discovery, but mediaSendCounter (${contact?.mediaSendCounter}) < ${userService.currentUser.requiredSendImages} or user is excluded ${contact?.userDiscoveryExcluded}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -352,10 +352,7 @@ Future<(Uint8List, Uint8List?)?> sendCipherText(
|
|||
|
||||
if (userService.currentUser.isUserDiscoveryEnabled && messageId != null) {
|
||||
final contact = await twonlyDB.contactsDao.getContactById(contactId);
|
||||
if (contact != null &&
|
||||
contact.mediaSendCounter >=
|
||||
userService.currentUser.requiredSendImages &&
|
||||
!contact.userDiscoveryExcluded) {
|
||||
if (UserDiscoveryService.isContactAllowed(contact)) {
|
||||
final version = await UserDiscoveryService.getCurrentVersion();
|
||||
if (version != null) {
|
||||
encryptedContent.senderUserDiscoveryVersion = version;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,31 @@ class UserDiscoveryService {
|
|||
}
|
||||
}
|
||||
|
||||
static bool isContactAllowed(Contact? c) {
|
||||
if (c == null) return false;
|
||||
final u = userService.currentUser;
|
||||
// Only accepted users are allowed.
|
||||
if (!c.accepted || c.blocked) return false;
|
||||
if (c.mediaSendCounter < u.requiredSendImages) return false;
|
||||
if (c.userDiscoveryExcluded) return false;
|
||||
if (u.userDiscoveryRequiresManualApproval &&
|
||||
(c.userDiscoveryManualApproved == null ||
|
||||
!c.userDiscoveryManualApproved!)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool shouldRequestManualApproval(Contact c) {
|
||||
final u = userService.currentUser;
|
||||
if (!u.isUserDiscoveryEnabled) return false;
|
||||
if (c.mediaSendCounter < u.requiredSendImages) return false;
|
||||
if (c.userDiscoveryExcluded) return false;
|
||||
if (!u.userDiscoveryRequiresManualApproval) return false;
|
||||
if (c.userDiscoveryManualApproved == true) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static Future<void> initializeOrUpdate({
|
||||
required int threshold,
|
||||
required bool sharePromotion,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ Future<void> handleUserStudyUpload() async {
|
|||
userService.currentUser.requiredSendImages,
|
||||
'user_discovery_threshold':
|
||||
userService.currentUser.userDiscoveryThreshold,
|
||||
'user_discovery_requires_manual_approval':
|
||||
userService.currentUser.userDiscoveryRequiresManualApproval,
|
||||
'user_discovery_share_promotion':
|
||||
userService.currentUser.userDiscoverySharePromotion,
|
||||
|
||||
'user_discovery_count_friends_shared': udFriendsShared.length,
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
import 'package:twonly/src/visual/views/camera/camera_send_to.view.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/bottom_sheets/share_additional.bottom_sheet.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/user_discovery_manual_approval.comp.dart';
|
||||
|
||||
class MessageInput extends StatefulWidget {
|
||||
const MessageInput({
|
||||
|
|
@ -198,6 +199,7 @@ class _MessageInputState extends State<MessageInput> {
|
|||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
UserDiscoveryManualApprovalComp(group: widget.group),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 10,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/user_discovery.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
|
||||
class UserDiscoveryManualApprovalComp extends StatefulWidget {
|
||||
const UserDiscoveryManualApprovalComp({required this.group, super.key});
|
||||
|
||||
final Group group;
|
||||
|
||||
@override
|
||||
State<UserDiscoveryManualApprovalComp> createState() =>
|
||||
_UserDiscoveryManualApprovalCompState();
|
||||
}
|
||||
|
||||
class _UserDiscoveryManualApprovalCompState
|
||||
extends State<UserDiscoveryManualApprovalComp> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<List<(Contact, GroupMember)>>(
|
||||
stream: twonlyDB.groupsDao.watchGroupMembers(widget.group.groupId),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final contactsToApprove = snapshot.data!
|
||||
.map((e) => e.$1)
|
||||
.where(UserDiscoveryService.shouldRequestManualApproval)
|
||||
.toList();
|
||||
|
||||
if (contactsToApprove.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: contactsToApprove.map((contact) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AvatarIcon(contactId: contact.userId, fontSize: 18),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.lang
|
||||
.userDiscoveryManualApprovalReachedThreshold(
|
||||
getContactDisplayName(contact),
|
||||
),
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contact.userId,
|
||||
const ContactsCompanion(
|
||||
userDiscoveryExcluded: Value(true),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
context.lang.userDiscoveryManualApprovalHideContact,
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contact.userId,
|
||||
const ContactsCompanion(
|
||||
userDiscoveryManualApproved: Value(true),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
context.lang.userDiscoveryManualApprovalShareContact,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,9 @@ import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
|||
import 'package:twonly/src/database/twonly.db.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/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/components/verification_badge.comp.dart';
|
||||
import 'package:twonly/src/visual/elements/headline.element.dart';
|
||||
import 'package:twonly/src/visual/themes/light.dart';
|
||||
import 'package:twonly/src/visual/views/contact/add_new_contact_components/friend_suggestions.comp.dart';
|
||||
|
|
@ -150,9 +150,6 @@ class OpenRequestsListComp extends StatelessWidget {
|
|||
),
|
||||
...contacts.map((contact) {
|
||||
Widget? subtitle;
|
||||
|
||||
Log.info('Relations count: ${relations.entries.length}');
|
||||
|
||||
for (final relation in relations.entries) {
|
||||
if (relation.key.announcedUserId == contact.userId) {
|
||||
subtitle = RichText(
|
||||
|
|
@ -165,11 +162,16 @@ class OpenRequestsListComp extends StatelessWidget {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
key: ValueKey(contact.userId),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(substringBy(contact.username, 25)),
|
||||
title: Row(
|
||||
children: [
|
||||
Text(substringBy(contact.username, 25)),
|
||||
const SizedBox(width: 3),
|
||||
VerificationBadgeComp(contact: contact),
|
||||
],
|
||||
),
|
||||
subtitle: subtitle,
|
||||
leading: AvatarIcon(
|
||||
contactId: contact.userId,
|
||||
|
|
|
|||
|
|
@ -306,35 +306,58 @@ class _ContactViewState extends State<ContactView> {
|
|||
],
|
||||
),
|
||||
if (userService.currentUser.isUserDiscoveryEnabled)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.usersViewfinder,
|
||||
text: context.lang.userDiscoverySettingsTitle,
|
||||
subtitle:
|
||||
!contact.userDiscoveryExcluded &&
|
||||
contact.mediaSendCounter <
|
||||
userService.currentUser.requiredSendImages
|
||||
? Text(
|
||||
context.lang.contactUserDiscoveryImagesLeft(
|
||||
userService.currentUser.requiredSendImages -
|
||||
contact.mediaSendCounter,
|
||||
getContactDisplayName(contact),
|
||||
),
|
||||
style: const TextStyle(fontSize: 9),
|
||||
)
|
||||
: null,
|
||||
trailing: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
value: !contact.userDiscoveryExcluded,
|
||||
onChanged: (a) async {
|
||||
await UserDiscoveryService.changeExclusionForContact(
|
||||
if (userService.currentUser.userDiscoveryRequiresManualApproval &&
|
||||
contact.userDiscoveryManualApproved != true)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.usersViewfinder,
|
||||
text: context.lang.userDiscoverySettingsTitle,
|
||||
subtitle: const Text(
|
||||
'Contact was not yet manual approved.',
|
||||
style: TextStyle(fontSize: 10),
|
||||
),
|
||||
trailing: TextButton(
|
||||
onPressed: () async {
|
||||
await twonlyDB.contactsDao.updateContact(
|
||||
contact.userId,
|
||||
!a,
|
||||
const ContactsCompanion(
|
||||
userDiscoveryManualApproved: Value(true),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text('Approve'),
|
||||
),
|
||||
)
|
||||
else
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.usersViewfinder,
|
||||
text: context.lang.userDiscoverySettingsTitle,
|
||||
subtitle:
|
||||
!contact.userDiscoveryExcluded &&
|
||||
contact.mediaSendCounter <
|
||||
userService.currentUser.requiredSendImages
|
||||
? Text(
|
||||
context.lang.contactUserDiscoveryImagesLeft(
|
||||
userService.currentUser.requiredSendImages -
|
||||
contact.mediaSendCounter,
|
||||
getContactDisplayName(contact),
|
||||
),
|
||||
style: const TextStyle(fontSize: 9),
|
||||
)
|
||||
: null,
|
||||
trailing: Transform.scale(
|
||||
scale: 0.8,
|
||||
child: Switch(
|
||||
value: !contact.userDiscoveryExcluded,
|
||||
onChanged: (a) async {
|
||||
await UserDiscoveryService.changeExclusionForContact(
|
||||
contact.userId,
|
||||
!a,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.flag,
|
||||
text: context.lang.reportUser,
|
||||
|
|
|
|||
|
|
@ -566,15 +566,33 @@ impl<Store: UserDiscoveryStore, Utils: UserDiscoveryUtils> UserDiscovery<Store,
|
|||
contact_id,
|
||||
announced_user.user_id
|
||||
);
|
||||
|
||||
// User is known, so add him to thr users relations
|
||||
self.store
|
||||
.push_new_user_relation(
|
||||
contact_id,
|
||||
announced_user,
|
||||
announced_user.clone(),
|
||||
public_key_verified_timestamp,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// As we no now the public_id from the user, all promotions up to this point are also known, so add these to the relations database as well
|
||||
let promotions = self
|
||||
.store
|
||||
.get_other_promotions_by_public_id(uda.public_id)
|
||||
.await?;
|
||||
|
||||
for promotion in promotions {
|
||||
self.store
|
||||
.push_new_user_relation(
|
||||
promotion.from_contact_id,
|
||||
announced_user,
|
||||
promotion.public_key_verified_timestamp,
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(UserDiscoveryError::ShamirsSecret(err.to_string())),
|
||||
|
|
|
|||
|
|
@ -145,17 +145,17 @@ class Contacts extends Table with TableInfo<Contacts, ContactsData> {
|
|||
'NOT NULL DEFAULT 0 CHECK (user_discovery_excluded IN (0, 1))',
|
||||
defaultValue: const CustomExpression('0'),
|
||||
);
|
||||
late final GeneratedColumn<int>
|
||||
userDiscoveryManualApproved = GeneratedColumn<int>(
|
||||
'user_discovery_manual_approved',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints:
|
||||
'NOT NULL DEFAULT 0 CHECK (user_discovery_manual_approved IN (0, 1))',
|
||||
defaultValue: const CustomExpression('0'),
|
||||
);
|
||||
late final GeneratedColumn<int> userDiscoveryManualApproved =
|
||||
GeneratedColumn<int>(
|
||||
'user_discovery_manual_approved',
|
||||
aliasedName,
|
||||
true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
$customConstraints:
|
||||
'NULL DEFAULT 0 CHECK (user_discovery_manual_approved IN (0, 1))',
|
||||
defaultValue: const CustomExpression('0'),
|
||||
);
|
||||
late final GeneratedColumn<int> mediaSendCounter = GeneratedColumn<int>(
|
||||
'media_send_counter',
|
||||
aliasedName,
|
||||
|
|
@ -269,7 +269,7 @@ class Contacts extends Table with TableInfo<Contacts, ContactsData> {
|
|||
userDiscoveryManualApproved: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}user_discovery_manual_approved'],
|
||||
)!,
|
||||
),
|
||||
mediaSendCounter: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int,
|
||||
data['${effectivePrefix}media_send_counter'],
|
||||
|
|
@ -308,7 +308,7 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
|
|||
final int createdAt;
|
||||
final i2.Uint8List? userDiscoveryVersion;
|
||||
final int userDiscoveryExcluded;
|
||||
final int userDiscoveryManualApproved;
|
||||
final int? userDiscoveryManualApproved;
|
||||
final int mediaSendCounter;
|
||||
final int mediaReceivedCounter;
|
||||
const ContactsData({
|
||||
|
|
@ -327,7 +327,7 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
|
|||
required this.createdAt,
|
||||
this.userDiscoveryVersion,
|
||||
required this.userDiscoveryExcluded,
|
||||
required this.userDiscoveryManualApproved,
|
||||
this.userDiscoveryManualApproved,
|
||||
required this.mediaSendCounter,
|
||||
required this.mediaReceivedCounter,
|
||||
});
|
||||
|
|
@ -361,9 +361,11 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
|
|||
);
|
||||
}
|
||||
map['user_discovery_excluded'] = Variable<int>(userDiscoveryExcluded);
|
||||
map['user_discovery_manual_approved'] = Variable<int>(
|
||||
userDiscoveryManualApproved,
|
||||
);
|
||||
if (!nullToAbsent || userDiscoveryManualApproved != null) {
|
||||
map['user_discovery_manual_approved'] = Variable<int>(
|
||||
userDiscoveryManualApproved,
|
||||
);
|
||||
}
|
||||
map['media_send_counter'] = Variable<int>(mediaSendCounter);
|
||||
map['media_received_counter'] = Variable<int>(mediaReceivedCounter);
|
||||
return map;
|
||||
|
|
@ -394,7 +396,10 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
|
|||
? const Value.absent()
|
||||
: Value(userDiscoveryVersion),
|
||||
userDiscoveryExcluded: Value(userDiscoveryExcluded),
|
||||
userDiscoveryManualApproved: Value(userDiscoveryManualApproved),
|
||||
userDiscoveryManualApproved:
|
||||
userDiscoveryManualApproved == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(userDiscoveryManualApproved),
|
||||
mediaSendCounter: Value(mediaSendCounter),
|
||||
mediaReceivedCounter: Value(mediaReceivedCounter),
|
||||
);
|
||||
|
|
@ -429,7 +434,7 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
|
|||
userDiscoveryExcluded: serializer.fromJson<int>(
|
||||
json['userDiscoveryExcluded'],
|
||||
),
|
||||
userDiscoveryManualApproved: serializer.fromJson<int>(
|
||||
userDiscoveryManualApproved: serializer.fromJson<int?>(
|
||||
json['userDiscoveryManualApproved'],
|
||||
),
|
||||
mediaSendCounter: serializer.fromJson<int>(json['mediaSendCounter']),
|
||||
|
|
@ -461,7 +466,7 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
|
|||
userDiscoveryVersion,
|
||||
),
|
||||
'userDiscoveryExcluded': serializer.toJson<int>(userDiscoveryExcluded),
|
||||
'userDiscoveryManualApproved': serializer.toJson<int>(
|
||||
'userDiscoveryManualApproved': serializer.toJson<int?>(
|
||||
userDiscoveryManualApproved,
|
||||
),
|
||||
'mediaSendCounter': serializer.toJson<int>(mediaSendCounter),
|
||||
|
|
@ -485,7 +490,7 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
|
|||
int? createdAt,
|
||||
Value<i2.Uint8List?> userDiscoveryVersion = const Value.absent(),
|
||||
int? userDiscoveryExcluded,
|
||||
int? userDiscoveryManualApproved,
|
||||
Value<int?> userDiscoveryManualApproved = const Value.absent(),
|
||||
int? mediaSendCounter,
|
||||
int? mediaReceivedCounter,
|
||||
}) => ContactsData(
|
||||
|
|
@ -508,8 +513,9 @@ class ContactsData extends DataClass implements Insertable<ContactsData> {
|
|||
? userDiscoveryVersion.value
|
||||
: this.userDiscoveryVersion,
|
||||
userDiscoveryExcluded: userDiscoveryExcluded ?? this.userDiscoveryExcluded,
|
||||
userDiscoveryManualApproved:
|
||||
userDiscoveryManualApproved ?? this.userDiscoveryManualApproved,
|
||||
userDiscoveryManualApproved: userDiscoveryManualApproved.present
|
||||
? userDiscoveryManualApproved.value
|
||||
: this.userDiscoveryManualApproved,
|
||||
mediaSendCounter: mediaSendCounter ?? this.mediaSendCounter,
|
||||
mediaReceivedCounter: mediaReceivedCounter ?? this.mediaReceivedCounter,
|
||||
);
|
||||
|
|
@ -649,7 +655,7 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
|
|||
final Value<int> createdAt;
|
||||
final Value<i2.Uint8List?> userDiscoveryVersion;
|
||||
final Value<int> userDiscoveryExcluded;
|
||||
final Value<int> userDiscoveryManualApproved;
|
||||
final Value<int?> userDiscoveryManualApproved;
|
||||
final Value<int> mediaSendCounter;
|
||||
final Value<int> mediaReceivedCounter;
|
||||
const ContactsCompanion({
|
||||
|
|
@ -756,7 +762,7 @@ class ContactsCompanion extends UpdateCompanion<ContactsData> {
|
|||
Value<int>? createdAt,
|
||||
Value<i2.Uint8List?>? userDiscoveryVersion,
|
||||
Value<int>? userDiscoveryExcluded,
|
||||
Value<int>? userDiscoveryManualApproved,
|
||||
Value<int?>? userDiscoveryManualApproved,
|
||||
Value<int>? mediaSendCounter,
|
||||
Value<int>? mediaReceivedCounter,
|
||||
}) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue