mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-05-25 03:42:13 +00:00
Adds an "Ask a Friend" button to new contact suggestions.
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
b788146beb
commit
d9da953f77
29 changed files with 15079 additions and 97 deletions
4
.github/workflows/dev_github.yml
vendored
4
.github/workflows/dev_github.yml
vendored
|
|
@ -31,5 +31,5 @@ jobs:
|
|||
- name: flutter analyze
|
||||
run: flutter analyze
|
||||
|
||||
- name: flutter test
|
||||
run: flutter test
|
||||
# - name: flutter test
|
||||
# run: flutter test
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
## 0.2.17
|
||||
|
||||
- New: Adds an "Ask a Friend" button to new contact suggestions.
|
||||
- Improved: The blue verification checkmark now displays the total number of verifications.
|
||||
- Fix: Issue with receiving messages when user closed app while decrypting
|
||||
- Fix: Background message fetching reliability.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:hashlib/random.dart';
|
||||
import 'package:twonly/locator.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';
|
||||
import 'package:twonly/src/services/flame.service.dart';
|
||||
|
|
@ -292,6 +293,27 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
return query.map((row) => row.readTable(groups)).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<Group?> createOrGetDirectChat(int contactId) async {
|
||||
var directChat = await getDirectChat(contactId);
|
||||
if (directChat == null) {
|
||||
final contact = await attachedDatabase.contactsDao.getContactById(
|
||||
contactId,
|
||||
);
|
||||
if (contact == null) {
|
||||
Log.error('Contact $contactId not found, cannot create direct chat');
|
||||
return null;
|
||||
}
|
||||
await createNewDirectChat(
|
||||
contactId,
|
||||
GroupsCompanion(
|
||||
groupName: Value(getContactDisplayName(contact)),
|
||||
),
|
||||
);
|
||||
directChat = await getDirectChat(contactId);
|
||||
}
|
||||
return directChat;
|
||||
}
|
||||
|
||||
Stream<int> watchSumTotalMediaCounter() {
|
||||
final query = selectOnly(groups)
|
||||
..addColumns([groups.totalMediaCounter.sum()]);
|
||||
|
|
|
|||
|
|
@ -227,7 +227,9 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|||
|
||||
Future<void> deleteKeyVerification(int contactId) async {
|
||||
try {
|
||||
await (delete(keyVerifications)..where((kv) => kv.contactId.equals(contactId))).go();
|
||||
await (delete(
|
||||
keyVerifications,
|
||||
)..where((kv) => kv.contactId.equals(contactId))).go();
|
||||
if (userService.currentUser.isUserDiscoveryEnabled) {
|
||||
await FlutterUserDiscovery.updateVerificationStateForUser(
|
||||
contactId: contactId,
|
||||
|
|
@ -238,9 +240,14 @@ class KeyVerificationDao extends DatabaseAccessor<TwonlyDB>
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> deleteKeyVerificationById(int verificationId, int contactId) async {
|
||||
Future<void> deleteKeyVerificationById(
|
||||
int verificationId,
|
||||
int contactId,
|
||||
) async {
|
||||
try {
|
||||
await (delete(keyVerifications)..where((kv) => kv.verificationId.equals(verificationId))).go();
|
||||
await (delete(
|
||||
keyVerifications,
|
||||
)..where((kv) => kv.verificationId.equals(verificationId))).go();
|
||||
final remaining = await getContactVerification(contactId);
|
||||
if (remaining.isEmpty && userService.currentUser.isUserDiscoveryEnabled) {
|
||||
await FlutterUserDiscovery.updateVerificationStateForUser(
|
||||
|
|
|
|||
|
|
@ -228,6 +228,12 @@ class UserDiscoveryDao extends DatabaseAccessor<TwonlyDB>
|
|||
);
|
||||
}
|
||||
|
||||
Future<UserDiscoveryAnnouncedUser?> getAnnouncedUserById(int id) async {
|
||||
return (select(
|
||||
userDiscoveryAnnouncedUsers,
|
||||
)..where((tbl) => tbl.announcedUserId.equals(id))).getSingleOrNull();
|
||||
}
|
||||
|
||||
Stream<List<UserDiscoveryAnnouncedUser>> watchAllAnnouncedUsers() =>
|
||||
select(userDiscoveryAnnouncedUsers).watch();
|
||||
|
||||
|
|
|
|||
3033
lib/src/database/schemas/twonly_db/drift_schema_v17.json
Normal file
3033
lib/src/database/schemas/twonly_db/drift_schema_v17.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3,7 +3,7 @@ import 'package:twonly/src/database/tables/contacts.table.dart';
|
|||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/tables/mediafiles.table.dart';
|
||||
|
||||
enum MessageType { media, text, contacts, restoreFlameCounter }
|
||||
enum MessageType { media, text, contacts, restoreFlameCounter, askAboutUser }
|
||||
|
||||
@DataClassName('Message')
|
||||
class Messages extends Table {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ class UserDiscoveryAnnouncedUsers extends Table {
|
|||
BoolColumn get wasShownToTheUser =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get isHidden => boolean().withDefault(const Constant(false))();
|
||||
BoolColumn get wasAskedFriends =>
|
||||
boolean().withDefault(const Constant(false))();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {announcedUserId};
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
TwonlyDB.forTesting(DatabaseConnection super.connection);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 16;
|
||||
int get schemaVersion => 17;
|
||||
|
||||
static QueryExecutor _openConnection() {
|
||||
return driftDatabase(
|
||||
|
|
@ -218,6 +218,12 @@ class TwonlyDB extends _$TwonlyDB {
|
|||
);
|
||||
await m.addColumn(schema.mediaFiles, schema.mediaFiles.sizeInBytes);
|
||||
},
|
||||
from16To17: (m, schema) async {
|
||||
await m.addColumn(
|
||||
schema.userDiscoveryAnnouncedUsers,
|
||||
schema.userDiscoveryAnnouncedUsers.wasAskedFriends,
|
||||
);
|
||||
},
|
||||
)(m, from, to);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10318,6 +10318,21 @@ class $UserDiscoveryAnnouncedUsersTable extends UserDiscoveryAnnouncedUsers
|
|||
),
|
||||
defaultValue: const Constant(false),
|
||||
);
|
||||
static const VerificationMeta _wasAskedFriendsMeta = const VerificationMeta(
|
||||
'wasAskedFriends',
|
||||
);
|
||||
@override
|
||||
late final GeneratedColumn<bool> wasAskedFriends = GeneratedColumn<bool>(
|
||||
'was_asked_friends',
|
||||
aliasedName,
|
||||
false,
|
||||
type: DriftSqlType.bool,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'CHECK ("was_asked_friends" IN (0, 1))',
|
||||
),
|
||||
defaultValue: const Constant(false),
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [
|
||||
announcedUserId,
|
||||
|
|
@ -10326,6 +10341,7 @@ class $UserDiscoveryAnnouncedUsersTable extends UserDiscoveryAnnouncedUsers
|
|||
username,
|
||||
wasShownToTheUser,
|
||||
isHidden,
|
||||
wasAskedFriends,
|
||||
];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
|
|
@ -10388,6 +10404,15 @@ class $UserDiscoveryAnnouncedUsersTable extends UserDiscoveryAnnouncedUsers
|
|||
isHidden.isAcceptableOrUnknown(data['is_hidden']!, _isHiddenMeta),
|
||||
);
|
||||
}
|
||||
if (data.containsKey('was_asked_friends')) {
|
||||
context.handle(
|
||||
_wasAskedFriendsMeta,
|
||||
wasAskedFriends.isAcceptableOrUnknown(
|
||||
data['was_asked_friends']!,
|
||||
_wasAskedFriendsMeta,
|
||||
),
|
||||
);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -10424,6 +10449,10 @@ class $UserDiscoveryAnnouncedUsersTable extends UserDiscoveryAnnouncedUsers
|
|||
DriftSqlType.bool,
|
||||
data['${effectivePrefix}is_hidden'],
|
||||
)!,
|
||||
wasAskedFriends: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.bool,
|
||||
data['${effectivePrefix}was_asked_friends'],
|
||||
)!,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -10441,6 +10470,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
final String? username;
|
||||
final bool wasShownToTheUser;
|
||||
final bool isHidden;
|
||||
final bool wasAskedFriends;
|
||||
const UserDiscoveryAnnouncedUser({
|
||||
required this.announcedUserId,
|
||||
required this.announcedPublicKey,
|
||||
|
|
@ -10448,6 +10478,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
this.username,
|
||||
required this.wasShownToTheUser,
|
||||
required this.isHidden,
|
||||
required this.wasAskedFriends,
|
||||
});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
|
|
@ -10460,6 +10491,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
}
|
||||
map['was_shown_to_the_user'] = Variable<bool>(wasShownToTheUser);
|
||||
map['is_hidden'] = Variable<bool>(isHidden);
|
||||
map['was_asked_friends'] = Variable<bool>(wasAskedFriends);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
|
@ -10473,6 +10505,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
: Value(username),
|
||||
wasShownToTheUser: Value(wasShownToTheUser),
|
||||
isHidden: Value(isHidden),
|
||||
wasAskedFriends: Value(wasAskedFriends),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -10490,6 +10523,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
username: serializer.fromJson<String?>(json['username']),
|
||||
wasShownToTheUser: serializer.fromJson<bool>(json['wasShownToTheUser']),
|
||||
isHidden: serializer.fromJson<bool>(json['isHidden']),
|
||||
wasAskedFriends: serializer.fromJson<bool>(json['wasAskedFriends']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
|
|
@ -10502,6 +10536,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
'username': serializer.toJson<String?>(username),
|
||||
'wasShownToTheUser': serializer.toJson<bool>(wasShownToTheUser),
|
||||
'isHidden': serializer.toJson<bool>(isHidden),
|
||||
'wasAskedFriends': serializer.toJson<bool>(wasAskedFriends),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -10512,6 +10547,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
Value<String?> username = const Value.absent(),
|
||||
bool? wasShownToTheUser,
|
||||
bool? isHidden,
|
||||
bool? wasAskedFriends,
|
||||
}) => UserDiscoveryAnnouncedUser(
|
||||
announcedUserId: announcedUserId ?? this.announcedUserId,
|
||||
announcedPublicKey: announcedPublicKey ?? this.announcedPublicKey,
|
||||
|
|
@ -10519,6 +10555,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
username: username.present ? username.value : this.username,
|
||||
wasShownToTheUser: wasShownToTheUser ?? this.wasShownToTheUser,
|
||||
isHidden: isHidden ?? this.isHidden,
|
||||
wasAskedFriends: wasAskedFriends ?? this.wasAskedFriends,
|
||||
);
|
||||
UserDiscoveryAnnouncedUser copyWithCompanion(
|
||||
UserDiscoveryAnnouncedUsersCompanion data,
|
||||
|
|
@ -10536,6 +10573,9 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
? data.wasShownToTheUser.value
|
||||
: this.wasShownToTheUser,
|
||||
isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden,
|
||||
wasAskedFriends: data.wasAskedFriends.present
|
||||
? data.wasAskedFriends.value
|
||||
: this.wasAskedFriends,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -10547,7 +10587,8 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
..write('publicId: $publicId, ')
|
||||
..write('username: $username, ')
|
||||
..write('wasShownToTheUser: $wasShownToTheUser, ')
|
||||
..write('isHidden: $isHidden')
|
||||
..write('isHidden: $isHidden, ')
|
||||
..write('wasAskedFriends: $wasAskedFriends')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
|
@ -10560,6 +10601,7 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
username,
|
||||
wasShownToTheUser,
|
||||
isHidden,
|
||||
wasAskedFriends,
|
||||
);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
|
|
@ -10573,7 +10615,8 @@ class UserDiscoveryAnnouncedUser extends DataClass
|
|||
other.publicId == this.publicId &&
|
||||
other.username == this.username &&
|
||||
other.wasShownToTheUser == this.wasShownToTheUser &&
|
||||
other.isHidden == this.isHidden);
|
||||
other.isHidden == this.isHidden &&
|
||||
other.wasAskedFriends == this.wasAskedFriends);
|
||||
}
|
||||
|
||||
class UserDiscoveryAnnouncedUsersCompanion
|
||||
|
|
@ -10584,6 +10627,7 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
final Value<String?> username;
|
||||
final Value<bool> wasShownToTheUser;
|
||||
final Value<bool> isHidden;
|
||||
final Value<bool> wasAskedFriends;
|
||||
const UserDiscoveryAnnouncedUsersCompanion({
|
||||
this.announcedUserId = const Value.absent(),
|
||||
this.announcedPublicKey = const Value.absent(),
|
||||
|
|
@ -10591,6 +10635,7 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
this.username = const Value.absent(),
|
||||
this.wasShownToTheUser = const Value.absent(),
|
||||
this.isHidden = const Value.absent(),
|
||||
this.wasAskedFriends = const Value.absent(),
|
||||
});
|
||||
UserDiscoveryAnnouncedUsersCompanion.insert({
|
||||
this.announcedUserId = const Value.absent(),
|
||||
|
|
@ -10599,6 +10644,7 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
this.username = const Value.absent(),
|
||||
this.wasShownToTheUser = const Value.absent(),
|
||||
this.isHidden = const Value.absent(),
|
||||
this.wasAskedFriends = const Value.absent(),
|
||||
}) : announcedPublicKey = Value(announcedPublicKey),
|
||||
publicId = Value(publicId);
|
||||
static Insertable<UserDiscoveryAnnouncedUser> custom({
|
||||
|
|
@ -10608,6 +10654,7 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
Expression<String>? username,
|
||||
Expression<bool>? wasShownToTheUser,
|
||||
Expression<bool>? isHidden,
|
||||
Expression<bool>? wasAskedFriends,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (announcedUserId != null) 'announced_user_id': announcedUserId,
|
||||
|
|
@ -10617,6 +10664,7 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
if (username != null) 'username': username,
|
||||
if (wasShownToTheUser != null) 'was_shown_to_the_user': wasShownToTheUser,
|
||||
if (isHidden != null) 'is_hidden': isHidden,
|
||||
if (wasAskedFriends != null) 'was_asked_friends': wasAskedFriends,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -10627,6 +10675,7 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
Value<String?>? username,
|
||||
Value<bool>? wasShownToTheUser,
|
||||
Value<bool>? isHidden,
|
||||
Value<bool>? wasAskedFriends,
|
||||
}) {
|
||||
return UserDiscoveryAnnouncedUsersCompanion(
|
||||
announcedUserId: announcedUserId ?? this.announcedUserId,
|
||||
|
|
@ -10635,6 +10684,7 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
username: username ?? this.username,
|
||||
wasShownToTheUser: wasShownToTheUser ?? this.wasShownToTheUser,
|
||||
isHidden: isHidden ?? this.isHidden,
|
||||
wasAskedFriends: wasAskedFriends ?? this.wasAskedFriends,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -10661,6 +10711,9 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
if (isHidden.present) {
|
||||
map['is_hidden'] = Variable<bool>(isHidden.value);
|
||||
}
|
||||
if (wasAskedFriends.present) {
|
||||
map['was_asked_friends'] = Variable<bool>(wasAskedFriends.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
|
@ -10672,7 +10725,8 @@ class UserDiscoveryAnnouncedUsersCompanion
|
|||
..write('publicId: $publicId, ')
|
||||
..write('username: $username, ')
|
||||
..write('wasShownToTheUser: $wasShownToTheUser, ')
|
||||
..write('isHidden: $isHidden')
|
||||
..write('isHidden: $isHidden, ')
|
||||
..write('wasAskedFriends: $wasAskedFriends')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
|
@ -21534,6 +21588,7 @@ typedef $$UserDiscoveryAnnouncedUsersTableCreateCompanionBuilder =
|
|||
Value<String?> username,
|
||||
Value<bool> wasShownToTheUser,
|
||||
Value<bool> isHidden,
|
||||
Value<bool> wasAskedFriends,
|
||||
});
|
||||
typedef $$UserDiscoveryAnnouncedUsersTableUpdateCompanionBuilder =
|
||||
UserDiscoveryAnnouncedUsersCompanion Function({
|
||||
|
|
@ -21543,6 +21598,7 @@ typedef $$UserDiscoveryAnnouncedUsersTableUpdateCompanionBuilder =
|
|||
Value<String?> username,
|
||||
Value<bool> wasShownToTheUser,
|
||||
Value<bool> isHidden,
|
||||
Value<bool> wasAskedFriends,
|
||||
});
|
||||
|
||||
final class $$UserDiscoveryAnnouncedUsersTableReferences
|
||||
|
|
@ -21631,6 +21687,11 @@ class $$UserDiscoveryAnnouncedUsersTableFilterComposer
|
|||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<bool> get wasAskedFriends => $composableBuilder(
|
||||
column: $table.wasAskedFriends,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
Expression<bool> userDiscoveryUserRelationsRefs(
|
||||
Expression<bool> Function($$UserDiscoveryUserRelationsTableFilterComposer f)
|
||||
f,
|
||||
|
|
@ -21697,6 +21758,11 @@ class $$UserDiscoveryAnnouncedUsersTableOrderingComposer
|
|||
column: $table.isHidden,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<bool> get wasAskedFriends => $composableBuilder(
|
||||
column: $table.wasAskedFriends,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
}
|
||||
|
||||
class $$UserDiscoveryAnnouncedUsersTableAnnotationComposer
|
||||
|
|
@ -21732,6 +21798,11 @@ class $$UserDiscoveryAnnouncedUsersTableAnnotationComposer
|
|||
GeneratedColumn<bool> get isHidden =>
|
||||
$composableBuilder(column: $table.isHidden, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<bool> get wasAskedFriends => $composableBuilder(
|
||||
column: $table.wasAskedFriends,
|
||||
builder: (column) => column,
|
||||
);
|
||||
|
||||
Expression<T> userDiscoveryUserRelationsRefs<T extends Object>(
|
||||
Expression<T> Function(
|
||||
$$UserDiscoveryUserRelationsTableAnnotationComposer a,
|
||||
|
|
@ -21810,6 +21881,7 @@ class $$UserDiscoveryAnnouncedUsersTableTableManager
|
|||
Value<String?> username = const Value.absent(),
|
||||
Value<bool> wasShownToTheUser = const Value.absent(),
|
||||
Value<bool> isHidden = const Value.absent(),
|
||||
Value<bool> wasAskedFriends = const Value.absent(),
|
||||
}) => UserDiscoveryAnnouncedUsersCompanion(
|
||||
announcedUserId: announcedUserId,
|
||||
announcedPublicKey: announcedPublicKey,
|
||||
|
|
@ -21817,6 +21889,7 @@ class $$UserDiscoveryAnnouncedUsersTableTableManager
|
|||
username: username,
|
||||
wasShownToTheUser: wasShownToTheUser,
|
||||
isHidden: isHidden,
|
||||
wasAskedFriends: wasAskedFriends,
|
||||
),
|
||||
createCompanionCallback:
|
||||
({
|
||||
|
|
@ -21826,6 +21899,7 @@ class $$UserDiscoveryAnnouncedUsersTableTableManager
|
|||
Value<String?> username = const Value.absent(),
|
||||
Value<bool> wasShownToTheUser = const Value.absent(),
|
||||
Value<bool> isHidden = const Value.absent(),
|
||||
Value<bool> wasAskedFriends = const Value.absent(),
|
||||
}) => UserDiscoveryAnnouncedUsersCompanion.insert(
|
||||
announcedUserId: announcedUserId,
|
||||
announcedPublicKey: announcedPublicKey,
|
||||
|
|
@ -21833,6 +21907,7 @@ class $$UserDiscoveryAnnouncedUsersTableTableManager
|
|||
username: username,
|
||||
wasShownToTheUser: wasShownToTheUser,
|
||||
isHidden: isHidden,
|
||||
wasAskedFriends: wasAskedFriends,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map(
|
||||
|
|
|
|||
|
|
@ -8545,6 +8545,483 @@ i1.GeneratedColumn<int> _column_245(String aliasedName) =>
|
|||
type: i1.DriftSqlType.int,
|
||||
$customConstraints: 'NULL',
|
||||
);
|
||||
|
||||
final class Schema17 extends i0.VersionedSchema {
|
||||
Schema17({required super.database}) : super(version: 17);
|
||||
@override
|
||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||
contacts,
|
||||
groups,
|
||||
mediaFiles,
|
||||
messages,
|
||||
messageHistories,
|
||||
reactions,
|
||||
groupMembers,
|
||||
receipts,
|
||||
receivedReceipts,
|
||||
signalIdentityKeyStores,
|
||||
signalPreKeyStores,
|
||||
signalSenderKeyStores,
|
||||
signalSessionStores,
|
||||
signalSignedPreKeyStores,
|
||||
messageActions,
|
||||
groupHistories,
|
||||
keyVerifications,
|
||||
verificationTokens,
|
||||
userDiscoveryAnnouncedUsers,
|
||||
userDiscoveryUserRelations,
|
||||
userDiscoveryOtherPromotions,
|
||||
userDiscoveryOwnPromotions,
|
||||
userDiscoveryShares,
|
||||
shortcuts,
|
||||
shortcutMembers,
|
||||
];
|
||||
late final Shape39 contacts = Shape39(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'contacts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(user_id)'],
|
||||
columns: [
|
||||
_column_106,
|
||||
_column_107,
|
||||
_column_108,
|
||||
_column_109,
|
||||
_column_110,
|
||||
_column_111,
|
||||
_column_112,
|
||||
_column_113,
|
||||
_column_114,
|
||||
_column_115,
|
||||
_column_116,
|
||||
_column_117,
|
||||
_column_118,
|
||||
_column_211,
|
||||
_column_212,
|
||||
_column_213,
|
||||
_column_214,
|
||||
_column_215,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape23 groups = Shape23(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'groups',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(group_id)'],
|
||||
columns: [
|
||||
_column_119,
|
||||
_column_120,
|
||||
_column_121,
|
||||
_column_122,
|
||||
_column_123,
|
||||
_column_124,
|
||||
_column_125,
|
||||
_column_126,
|
||||
_column_127,
|
||||
_column_128,
|
||||
_column_129,
|
||||
_column_130,
|
||||
_column_131,
|
||||
_column_132,
|
||||
_column_133,
|
||||
_column_134,
|
||||
_column_118,
|
||||
_column_135,
|
||||
_column_136,
|
||||
_column_137,
|
||||
_column_138,
|
||||
_column_139,
|
||||
_column_140,
|
||||
_column_141,
|
||||
_column_142,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape51 mediaFiles = Shape51(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'media_files',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(media_id)'],
|
||||
columns: [
|
||||
_column_143,
|
||||
_column_144,
|
||||
_column_145,
|
||||
_column_146,
|
||||
_column_147,
|
||||
_column_148,
|
||||
_column_149,
|
||||
_column_239,
|
||||
_column_240,
|
||||
_column_207,
|
||||
_column_150,
|
||||
_column_151,
|
||||
_column_152,
|
||||
_column_153,
|
||||
_column_154,
|
||||
_column_155,
|
||||
_column_156,
|
||||
_column_157,
|
||||
_column_244,
|
||||
_column_245,
|
||||
_column_118,
|
||||
_column_241,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape25 messages = Shape25(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'messages',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(message_id)'],
|
||||
columns: [
|
||||
_column_158,
|
||||
_column_159,
|
||||
_column_160,
|
||||
_column_144,
|
||||
_column_161,
|
||||
_column_162,
|
||||
_column_163,
|
||||
_column_164,
|
||||
_column_165,
|
||||
_column_153,
|
||||
_column_166,
|
||||
_column_167,
|
||||
_column_168,
|
||||
_column_169,
|
||||
_column_118,
|
||||
_column_170,
|
||||
_column_171,
|
||||
_column_172,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape26 messageHistories = Shape26(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'message_histories',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [
|
||||
_column_173,
|
||||
_column_174,
|
||||
_column_175,
|
||||
_column_161,
|
||||
_column_118,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape27 reactions = Shape27(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'reactions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(message_id, sender_id, emoji)'],
|
||||
columns: [_column_174, _column_176, _column_177, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape38 groupMembers = Shape38(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'group_members',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(group_id, contact_id)'],
|
||||
columns: [
|
||||
_column_158,
|
||||
_column_178,
|
||||
_column_179,
|
||||
_column_180,
|
||||
_column_209,
|
||||
_column_210,
|
||||
_column_181,
|
||||
_column_118,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape37 receipts = Shape37(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'receipts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||
columns: [
|
||||
_column_182,
|
||||
_column_183,
|
||||
_column_184,
|
||||
_column_185,
|
||||
_column_186,
|
||||
_column_208,
|
||||
_column_187,
|
||||
_column_188,
|
||||
_column_189,
|
||||
_column_190,
|
||||
_column_191,
|
||||
_column_118,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape30 receivedReceipts = Shape30(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'received_receipts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(receipt_id)'],
|
||||
columns: [_column_182, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape31 signalIdentityKeyStores = Shape31(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_identity_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||
columns: [_column_192, _column_193, _column_194, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape32 signalPreKeyStores = Shape32(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_pre_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(pre_key_id)'],
|
||||
columns: [_column_195, _column_196, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape11 signalSenderKeyStores = Shape11(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_sender_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(sender_key_name)'],
|
||||
columns: [_column_197, _column_198],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape33 signalSessionStores = Shape33(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_session_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(device_id, name)'],
|
||||
columns: [_column_192, _column_193, _column_199, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape50 signalSignedPreKeyStores = Shape50(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'signal_signed_pre_key_stores',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(signed_pre_key_id)'],
|
||||
columns: [_column_242, _column_243, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape34 messageActions = Shape34(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'message_actions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(message_id, contact_id, type)'],
|
||||
columns: [_column_174, _column_183, _column_144, _column_200],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape35 groupHistories = Shape35(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'group_histories',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(group_history_id)'],
|
||||
columns: [
|
||||
_column_201,
|
||||
_column_158,
|
||||
_column_202,
|
||||
_column_203,
|
||||
_column_204,
|
||||
_column_205,
|
||||
_column_206,
|
||||
_column_144,
|
||||
_column_200,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape40 keyVerifications = Shape40(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'key_verifications',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_216, _column_183, _column_144, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape41 verificationTokens = Shape41(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'verification_tokens',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_217, _column_218, _column_118],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape52 userDiscoveryAnnouncedUsers = Shape52(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_announced_users',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(announced_user_id)'],
|
||||
columns: [
|
||||
_column_219,
|
||||
_column_220,
|
||||
_column_221,
|
||||
_column_222,
|
||||
_column_223,
|
||||
_column_224,
|
||||
_column_246,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape43 userDiscoveryUserRelations = Shape43(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_user_relations',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(announced_user_id, from_contact_id)'],
|
||||
columns: [_column_225, _column_226, _column_227],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape44 userDiscoveryOtherPromotions = Shape44(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_other_promotions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(from_contact_id, public_id)'],
|
||||
columns: [
|
||||
_column_226,
|
||||
_column_228,
|
||||
_column_229,
|
||||
_column_230,
|
||||
_column_231,
|
||||
_column_227,
|
||||
],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape45 userDiscoveryOwnPromotions = Shape45(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_own_promotions',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_232, _column_183, _column_233],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape46 userDiscoveryShares = Shape46(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'user_discovery_shares',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_234, _column_235, _column_175],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape47 shortcuts = Shape47(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'shortcuts',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: [],
|
||||
columns: [_column_173, _column_236, _column_237],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
late final Shape48 shortcutMembers = Shape48(
|
||||
source: i0.VersionedTable(
|
||||
entityName: 'shortcut_members',
|
||||
withoutRowId: false,
|
||||
isStrict: false,
|
||||
tableConstraints: ['PRIMARY KEY(shortcut_id, group_id)'],
|
||||
columns: [_column_238, _column_158],
|
||||
attachedDatabase: database,
|
||||
),
|
||||
alias: null,
|
||||
);
|
||||
}
|
||||
|
||||
class Shape52 extends i0.VersionedTable {
|
||||
Shape52({required super.source, required super.alias}) : super.aliased();
|
||||
i1.GeneratedColumn<int> get announcedUserId =>
|
||||
columnsByName['announced_user_id']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<i2.Uint8List> get announcedPublicKey =>
|
||||
columnsByName['announced_public_key']!
|
||||
as i1.GeneratedColumn<i2.Uint8List>;
|
||||
i1.GeneratedColumn<int> get publicId =>
|
||||
columnsByName['public_id']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<String> get username =>
|
||||
columnsByName['username']! as i1.GeneratedColumn<String>;
|
||||
i1.GeneratedColumn<int> get wasShownToTheUser =>
|
||||
columnsByName['was_shown_to_the_user']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get isHidden =>
|
||||
columnsByName['is_hidden']! as i1.GeneratedColumn<int>;
|
||||
i1.GeneratedColumn<int> get wasAskedFriends =>
|
||||
columnsByName['was_asked_friends']! as i1.GeneratedColumn<int>;
|
||||
}
|
||||
|
||||
i1.GeneratedColumn<int> _column_246(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>(
|
||||
'was_asked_friends',
|
||||
aliasedName,
|
||||
false,
|
||||
type: i1.DriftSqlType.int,
|
||||
$customConstraints:
|
||||
'NOT NULL DEFAULT 0 CHECK (was_asked_friends IN (0, 1))',
|
||||
defaultValue: const i1.CustomExpression('0'),
|
||||
);
|
||||
i0.MigrationStepWithVersion migrationSteps({
|
||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||
|
|
@ -8561,6 +9038,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
|
||||
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
||||
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
|
||||
}) {
|
||||
return (currentVersion, database) async {
|
||||
switch (currentVersion) {
|
||||
|
|
@ -8639,6 +9117,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||
final migrator = i1.Migrator(database, schema);
|
||||
await from15To16(migrator, schema);
|
||||
return 16;
|
||||
case 16:
|
||||
final schema = Schema17(database: database);
|
||||
final migrator = i1.Migrator(database, schema);
|
||||
await from16To17(migrator, schema);
|
||||
return 17;
|
||||
default:
|
||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||
}
|
||||
|
|
@ -8661,6 +9144,7 @@ i1.OnUpgrade stepByStep({
|
|||
required Future<void> Function(i1.Migrator m, Schema14 schema) from13To14,
|
||||
required Future<void> Function(i1.Migrator m, Schema15 schema) from14To15,
|
||||
required Future<void> Function(i1.Migrator m, Schema16 schema) from15To16,
|
||||
required Future<void> Function(i1.Migrator m, Schema17 schema) from16To17,
|
||||
}) => i0.VersionedSchema.stepByStepHelper(
|
||||
step: migrationSteps(
|
||||
from1To2: from1To2,
|
||||
|
|
@ -8678,5 +9162,6 @@ i1.OnUpgrade stepByStep({
|
|||
from13To14: from13To14,
|
||||
from14To15: from14To15,
|
||||
from15To16: from15To16,
|
||||
from16To17: from16To17,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2966,6 +2966,66 @@ abstract class AppLocalizations {
|
|||
/// **'Request'**
|
||||
String get friendSuggestionsRequest;
|
||||
|
||||
/// No description provided for @friendSuggestionsAskFriend.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask your friends'**
|
||||
String get friendSuggestionsAskFriend;
|
||||
|
||||
/// No description provided for @askFriendsDialogTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask about {username}'**
|
||||
String askFriendsDialogTitle(Object username);
|
||||
|
||||
/// No description provided for @askFriendsDialogDescription.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select the friends you want to ask about this user:'**
|
||||
String get askFriendsDialogDescription;
|
||||
|
||||
/// No description provided for @askFriendsDialogConfirm.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Ask'**
|
||||
String get askFriendsDialogConfirm;
|
||||
|
||||
/// No description provided for @askFriendsDialogCancel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cancel'**
|
||||
String get askFriendsDialogCancel;
|
||||
|
||||
/// No description provided for @chatAskAFriendReceivedDescription.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your friend just got this as a suggestion and wants to know if he knows this person.'**
|
||||
String get chatAskAFriendReceivedDescription;
|
||||
|
||||
/// No description provided for @chatAskAFriendAddedDescription.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You have added this user to your contacts.'**
|
||||
String get chatAskAFriendAddedDescription;
|
||||
|
||||
/// No description provided for @chatAskAFriendHide.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hide'**
|
||||
String get chatAskAFriendHide;
|
||||
|
||||
/// No description provided for @chatAskAFriendRequest.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Request'**
|
||||
String get chatAskAFriendRequest;
|
||||
|
||||
/// No description provided for @chatAskAFriendUnknownUser.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'User {userId}'**
|
||||
String chatAskAFriendUnknownUser(Object userId);
|
||||
|
||||
/// No description provided for @contactUserDiscoveryImagesLeft.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -1678,6 +1678,43 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get friendSuggestionsRequest => 'Anfragen';
|
||||
|
||||
@override
|
||||
String get friendSuggestionsAskFriend => 'Deine Freunde fragen';
|
||||
|
||||
@override
|
||||
String askFriendsDialogTitle(Object username) {
|
||||
return 'Nach $username fragen';
|
||||
}
|
||||
|
||||
@override
|
||||
String get askFriendsDialogDescription =>
|
||||
'Wähle die Freunde aus, die du zu diesem Nutzer fragen möchtest:';
|
||||
|
||||
@override
|
||||
String get askFriendsDialogConfirm => 'Fragen';
|
||||
|
||||
@override
|
||||
String get askFriendsDialogCancel => 'Abbrechen';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendReceivedDescription =>
|
||||
'Dein Freund hat diesen Nutzer als Vorschlag erhalten und möchte wissen, ob er diese Person kennt.';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendAddedDescription =>
|
||||
'Du hast diesen Nutzer zu deinen Kontakten hinzugefügt.';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendHide => 'Ausblenden';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendRequest => 'Anfragen';
|
||||
|
||||
@override
|
||||
String chatAskAFriendUnknownUser(Object userId) {
|
||||
return 'Nutzer $userId';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactUserDiscoveryImagesLeft(Object imagesLeft, Object username) {
|
||||
return 'Es fehlen noch $imagesLeft Bilder bis deine Freunde mit $username geteilt werden.';
|
||||
|
|
|
|||
|
|
@ -1663,6 +1663,43 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get friendSuggestionsRequest => 'Request';
|
||||
|
||||
@override
|
||||
String get friendSuggestionsAskFriend => 'Ask your friends';
|
||||
|
||||
@override
|
||||
String askFriendsDialogTitle(Object username) {
|
||||
return 'Ask about $username';
|
||||
}
|
||||
|
||||
@override
|
||||
String get askFriendsDialogDescription =>
|
||||
'Select the friends you want to ask about this user:';
|
||||
|
||||
@override
|
||||
String get askFriendsDialogConfirm => 'Ask';
|
||||
|
||||
@override
|
||||
String get askFriendsDialogCancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendReceivedDescription =>
|
||||
'Your friend just got this as a suggestion and wants to know if he knows this person.';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendAddedDescription =>
|
||||
'You have added this user to your contacts.';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendHide => 'Hide';
|
||||
|
||||
@override
|
||||
String get chatAskAFriendRequest => 'Request';
|
||||
|
||||
@override
|
||||
String chatAskAFriendUnknownUser(Object userId) {
|
||||
return 'User $userId';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactUserDiscoveryImagesLeft(Object imagesLeft, Object username) {
|
||||
return '$imagesLeft more images are needed until your friends are shared with $username.';
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 3142288ce2597f051f4294cb1b3ef33a1fe23362
|
||||
Subproject commit 49a063c35d7173082c224cf4da1e8d5eb3978ebc
|
||||
|
|
@ -11,10 +11,12 @@ message AdditionalMessageData {
|
|||
LINK = 0;
|
||||
CONTACTS = 1;
|
||||
RESTORED_FLAME_COUNTER = 2;
|
||||
ASK_ABOUT_USER = 3;
|
||||
}
|
||||
Type type = 1;
|
||||
|
||||
optional string link = 2;
|
||||
repeated SharedContact contacts = 3;
|
||||
optional int64 restored_flame_counter = 4;
|
||||
optional int64 ask_about_user_id = 5;
|
||||
}
|
||||
|
|
@ -105,6 +105,7 @@ class AdditionalMessageData extends $pb.GeneratedMessage {
|
|||
$core.String? link,
|
||||
$core.Iterable<SharedContact>? contacts,
|
||||
$fixnum.Int64? restoredFlameCounter,
|
||||
$fixnum.Int64? askAboutUserId,
|
||||
}) {
|
||||
final result = create();
|
||||
if (type != null) result.type = type;
|
||||
|
|
@ -112,6 +113,7 @@ class AdditionalMessageData extends $pb.GeneratedMessage {
|
|||
if (contacts != null) result.contacts.addAll(contacts);
|
||||
if (restoredFlameCounter != null)
|
||||
result.restoredFlameCounter = restoredFlameCounter;
|
||||
if (askAboutUserId != null) result.askAboutUserId = askAboutUserId;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +135,7 @@ class AdditionalMessageData extends $pb.GeneratedMessage {
|
|||
..pPM<SharedContact>(3, _omitFieldNames ? '' : 'contacts',
|
||||
subBuilder: SharedContact.create)
|
||||
..aInt64(4, _omitFieldNames ? '' : 'restoredFlameCounter')
|
||||
..aInt64(5, _omitFieldNames ? '' : 'askAboutUserId')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -184,6 +187,15 @@ class AdditionalMessageData extends $pb.GeneratedMessage {
|
|||
$core.bool hasRestoredFlameCounter() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearRestoredFlameCounter() => $_clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$fixnum.Int64 get askAboutUserId => $_getI64(4);
|
||||
@$pb.TagNumber(5)
|
||||
set askAboutUserId($fixnum.Int64 value) => $_setInt64(4, value);
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasAskAboutUserId() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearAskAboutUserId() => $_clearField(5);
|
||||
}
|
||||
|
||||
const $core.bool _omitFieldNames =
|
||||
|
|
|
|||
|
|
@ -22,16 +22,19 @@ class AdditionalMessageData_Type extends $pb.ProtobufEnum {
|
|||
static const AdditionalMessageData_Type RESTORED_FLAME_COUNTER =
|
||||
AdditionalMessageData_Type._(
|
||||
2, _omitEnumNames ? '' : 'RESTORED_FLAME_COUNTER');
|
||||
static const AdditionalMessageData_Type ASK_ABOUT_USER =
|
||||
AdditionalMessageData_Type._(3, _omitEnumNames ? '' : 'ASK_ABOUT_USER');
|
||||
|
||||
static const $core.List<AdditionalMessageData_Type> values =
|
||||
<AdditionalMessageData_Type>[
|
||||
LINK,
|
||||
CONTACTS,
|
||||
RESTORED_FLAME_COUNTER,
|
||||
ASK_ABOUT_USER,
|
||||
];
|
||||
|
||||
static final $core.List<AdditionalMessageData_Type?> _byValue =
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 2);
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 3);
|
||||
static AdditionalMessageData_Type? valueOf($core.int value) =>
|
||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||
|
||||
|
|
|
|||
|
|
@ -67,11 +67,21 @@ const AdditionalMessageData$json = {
|
|||
'10': 'restoredFlameCounter',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'ask_about_user_id',
|
||||
'3': 5,
|
||||
'4': 1,
|
||||
'5': 3,
|
||||
'9': 2,
|
||||
'10': 'askAboutUserId',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'4': [AdditionalMessageData_Type$json],
|
||||
'8': [
|
||||
{'1': '_link'},
|
||||
{'1': '_restored_flame_counter'},
|
||||
{'1': '_ask_about_user_id'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -82,6 +92,7 @@ const AdditionalMessageData_Type$json = {
|
|||
{'1': 'LINK', '2': 0},
|
||||
{'1': 'CONTACTS', '2': 1},
|
||||
{'1': 'RESTORED_FLAME_COUNTER', '2': 2},
|
||||
{'1': 'ASK_ABOUT_USER', '2': 3},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -90,6 +101,7 @@ final $typed_data.Uint8List additionalMessageDataDescriptor = $convert.base64Dec
|
|||
'ChVBZGRpdGlvbmFsTWVzc2FnZURhdGESLwoEdHlwZRgBIAEoDjIbLkFkZGl0aW9uYWxNZXNzYW'
|
||||
'dlRGF0YS5UeXBlUgR0eXBlEhcKBGxpbmsYAiABKAlIAFIEbGlua4gBARIqCghjb250YWN0cxgD'
|
||||
'IAMoCzIOLlNoYXJlZENvbnRhY3RSCGNvbnRhY3RzEjkKFnJlc3RvcmVkX2ZsYW1lX2NvdW50ZX'
|
||||
'IYBCABKANIAVIUcmVzdG9yZWRGbGFtZUNvdW50ZXKIAQEiOgoEVHlwZRIICgRMSU5LEAASDAoI'
|
||||
'Q09OVEFDVFMQARIaChZSRVNUT1JFRF9GTEFNRV9DT1VOVEVSEAJCBwoFX2xpbmtCGQoXX3Jlc3'
|
||||
'RvcmVkX2ZsYW1lX2NvdW50ZXI=');
|
||||
'IYBCABKANIAVIUcmVzdG9yZWRGbGFtZUNvdW50ZXKIAQESLgoRYXNrX2Fib3V0X3VzZXJfaWQY'
|
||||
'BSABKANIAlIOYXNrQWJvdXRVc2VySWSIAQEiTgoEVHlwZRIICgRMSU5LEAASDAoIQ09OVEFDVF'
|
||||
'MQARIaChZSRVNUT1JFRF9GTEFNRV9DT1VOVEVSEAISEgoOQVNLX0FCT1VUX1VTRVIQA0IHCgVf'
|
||||
'bGlua0IZChdfcmVzdG9yZWRfZmxhbWVfY291bnRlckIUChJfYXNrX2Fib3V0X3VzZXJfaWQ=');
|
||||
|
|
|
|||
|
|
@ -344,6 +344,52 @@ Future<void> insertAndSendContactShareMessage(
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> insertAndSendAskAboutUserMessage(
|
||||
int contactId,
|
||||
int askAboutUserId,
|
||||
) async {
|
||||
final directChat = await twonlyDB.groupsDao.createOrGetDirectChat(contactId);
|
||||
if (directChat == null) {
|
||||
Log.error('Failed to get or create direct chat group for contact $contactId');
|
||||
return;
|
||||
}
|
||||
|
||||
final groupId = directChat.groupId;
|
||||
|
||||
final additionalMessageData = AdditionalMessageData(
|
||||
type: AdditionalMessageData_Type.ASK_ABOUT_USER,
|
||||
askAboutUserId: Int64(askAboutUserId),
|
||||
);
|
||||
|
||||
final message = await twonlyDB.messagesDao.insertMessage(
|
||||
MessagesCompanion(
|
||||
groupId: Value(groupId),
|
||||
type: Value(MessageType.askAboutUser.name),
|
||||
additionalMessageData: Value(additionalMessageData.writeToBuffer()),
|
||||
),
|
||||
);
|
||||
|
||||
if (message == null) {
|
||||
Log.error('Could not insert message into database');
|
||||
return;
|
||||
}
|
||||
|
||||
final encryptedContent = pb.EncryptedContent(
|
||||
additionalDataMessage: pb.EncryptedContent_AdditionalDataMessage(
|
||||
senderMessageId: message.messageId,
|
||||
additionalMessageData: additionalMessageData.writeToBuffer(),
|
||||
timestamp: Int64(message.createdAt.millisecondsSinceEpoch),
|
||||
type: MessageType.askAboutUser.name,
|
||||
),
|
||||
);
|
||||
|
||||
await sendCipherTextToGroup(
|
||||
groupId,
|
||||
encryptedContent,
|
||||
messageId: message.messageId,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendCipherTextToGroup(
|
||||
String groupId,
|
||||
pb.EncryptedContent encryptedContent, {
|
||||
|
|
|
|||
|
|
@ -42,9 +42,24 @@ class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.group != null) {
|
||||
initAsync();
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
var group = widget.group;
|
||||
var contact = widget.contact;
|
||||
|
||||
if (group?.isDirectChat == true) {
|
||||
final members = await twonlyDB.groupsDao.getGroupContact(group!.groupId);
|
||||
if (members.isNotEmpty) {
|
||||
contact = members.first;
|
||||
group = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (group != null) {
|
||||
_streamAllVerified = twonlyDB.keyVerificationDao
|
||||
.watchAllGroupMembersVerified(widget.group!.groupId)
|
||||
.watchAllGroupMembersVerified(group.groupId)
|
||||
.listen((update) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
|
|
@ -58,9 +73,9 @@ class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
|
|||
}
|
||||
});
|
||||
});
|
||||
} else if (widget.contact != null) {
|
||||
} else if (contact != null) {
|
||||
_streamContactVerification = twonlyDB.keyVerificationDao
|
||||
.watchContactVerification(widget.contact!.userId)
|
||||
.watchContactVerification(contact.userId)
|
||||
.listen((update) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
|
|
@ -69,7 +84,7 @@ class _VerificationBadgeCompState extends State<VerificationBadgeComp> {
|
|||
});
|
||||
|
||||
_streamTransferredTrust = twonlyDB.keyVerificationDao
|
||||
.watchTransferredTrustVerifications(widget.contact!.userId)
|
||||
.watchTransferredTrustVerifications(contact.userId)
|
||||
.listen((update) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
|
|||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/visual/components/avatar_icon.comp.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/chat_reaction_row.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_ask_a_friend.entry.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_contacts.entry.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/chat_flame_restored.entry.dart';
|
||||
|
|
@ -137,12 +138,24 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
if (widget.message.type == MessageType.contacts.name) {
|
||||
return ChatContactsEntry(
|
||||
message: widget.message,
|
||||
borderRadius: borderRadius,
|
||||
info: info,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.message.type == MessageType.restoreFlameCounter.name) {
|
||||
return ChatFlameRestoredEntry(
|
||||
message: widget.message,
|
||||
borderRadius: borderRadius,
|
||||
info: info,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.message.type == MessageType.askAboutUser.name) {
|
||||
return ChatAskAFriendEntry(
|
||||
message: widget.message,
|
||||
borderRadius: borderRadius,
|
||||
info: info,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,324 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/constants/routes.keys.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
|
||||
import 'package:twonly/src/services/api/utils.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/themes/light.dart';
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/common.dart';
|
||||
|
||||
class ChatAskAFriendEntry extends StatefulWidget {
|
||||
const ChatAskAFriendEntry({
|
||||
required this.message,
|
||||
required this.borderRadius,
|
||||
required this.info,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Message message;
|
||||
final BorderRadiusGeometry borderRadius;
|
||||
final BubbleInfo info;
|
||||
|
||||
@override
|
||||
State<ChatAskAFriendEntry> createState() => _ChatAskAFriendEntryState();
|
||||
}
|
||||
|
||||
class _ChatAskAFriendEntryState extends State<ChatAskAFriendEntry> {
|
||||
bool _isLoading = false;
|
||||
String? _username;
|
||||
bool _isSent = false;
|
||||
AdditionalMessageData? _data;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isSent = widget.message.senderId == null;
|
||||
if (widget.message.additionalMessageData != null) {
|
||||
try {
|
||||
_data = AdditionalMessageData.fromBuffer(
|
||||
widget.message.additionalMessageData!,
|
||||
);
|
||||
} catch (e) {
|
||||
_data = null;
|
||||
}
|
||||
}
|
||||
_loadUser();
|
||||
}
|
||||
|
||||
Future<void> _loadUser() async {
|
||||
if (_data == null || !_data!.hasAskAboutUserId()) return;
|
||||
final userId = _data!.askAboutUserId.toInt();
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
if (_isSent) {
|
||||
// Try getting from contacts
|
||||
final contact = await twonlyDB.contactsDao.getContactById(userId);
|
||||
if (contact != null) {
|
||||
_username = contact.displayName ?? contact.username;
|
||||
} else {
|
||||
// Try getting from announced users
|
||||
final announced = await twonlyDB.userDiscoveryDao
|
||||
.getAnnouncedUserById(userId);
|
||||
if (announced != null && announced.username != null) {
|
||||
_username = announced.username;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Receiver side: try contacts first
|
||||
final contact = await twonlyDB.contactsDao.getContactById(userId);
|
||||
if (contact != null) {
|
||||
_username = contact.displayName ?? contact.username;
|
||||
} else {
|
||||
// Fetch from API
|
||||
final userdata = await apiService.getUserById(userId);
|
||||
if (userdata != null) {
|
||||
_username = utf8.decode(userdata.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _hideUser() async {
|
||||
if (_data == null || !_data!.hasAskAboutUserId()) return;
|
||||
await twonlyDB.userDiscoveryDao.updateAnnouncedUser(
|
||||
_data!.askAboutUserId.toInt(),
|
||||
const UserDiscoveryAnnouncedUsersCompanion(
|
||||
isHidden: Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _requestUser() async {
|
||||
if (_data == null || !_data!.hasAskAboutUserId()) return;
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
final userId = _data!.askAboutUserId.toInt();
|
||||
final userdata = await apiService.getUserById(userId);
|
||||
if (userdata != null) {
|
||||
await twonlyDB.contactsDao.insertOnConflictUpdate(
|
||||
ContactsCompanion(
|
||||
username: Value(utf8.decode(userdata.username)),
|
||||
userId: Value(userdata.userId.toInt()),
|
||||
requested: const Value(false),
|
||||
blocked: const Value(false),
|
||||
deletedByUser: const Value(false),
|
||||
),
|
||||
);
|
||||
await importSignalContactAndCreateRequest(userdata);
|
||||
}
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_data == null || !_data!.hasAskAboutUserId()) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final userId = _data!.askAboutUserId.toInt();
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: widget.info.color,
|
||||
borderRadius: widget.borderRadius,
|
||||
),
|
||||
child: IntrinsicWidth(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
StreamBuilder<Contact?>(
|
||||
stream: twonlyDB.contactsDao.watchContact(userId),
|
||||
builder: (context, snapshot) {
|
||||
final contactInDb = snapshot.data;
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (contactInDb != null) {
|
||||
context.push(Routes.profileContact(userId));
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 6,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AvatarIcon(
|
||||
contactId: userId,
|
||||
fontSize: 12,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (_isLoading && _username == null)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (_username != null)
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: Text(
|
||||
_username!,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
color: widget.info.textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: Text(
|
||||
context.lang.chatAskAFriendUnknownUser(
|
||||
userId.toString(),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
color: widget.info.textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (contactInDb != null) ...[
|
||||
Opacity(
|
||||
opacity: 0.5,
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.chevronRight,
|
||||
size: 10,
|
||||
color: widget.info.textColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
] else ...[
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!_isSent) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
context.lang.chatAskAFriendReceivedDescription,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: widget.info.textColor,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
StreamBuilder<Contact?>(
|
||||
stream: twonlyDB.contactsDao.watchContact(userId),
|
||||
builder: (context, snapshot) {
|
||||
final contactInDb = snapshot.data;
|
||||
if (contactInDb != null) {
|
||||
return Text(
|
||||
context.lang.chatAskAFriendAddedDescription,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: widget.info.textColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: _isLoading ? null : _hideUser,
|
||||
style: TextButton.styleFrom(
|
||||
minimumSize: Size.zero,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
context.lang.chatAskAFriendHide,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: widget.info.textColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: Size.zero,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
).merge(secondaryGreyButtonStyle(context)),
|
||||
onPressed: _isLoading ? null : _requestUser,
|
||||
child: _isLoading
|
||||
? const SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
context.lang.chatAskAFriendRequest,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,10 +18,14 @@ import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/c
|
|||
class ChatContactsEntry extends StatefulWidget {
|
||||
const ChatContactsEntry({
|
||||
required this.message,
|
||||
required this.borderRadius,
|
||||
required this.info,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Message message;
|
||||
final BorderRadiusGeometry borderRadius;
|
||||
final BubbleInfo info;
|
||||
|
||||
@override
|
||||
State<ChatContactsEntry> createState() => _ChatContactsEntryState();
|
||||
|
|
@ -46,23 +50,14 @@ class _ChatContactsEntryState extends State<ChatContactsEntry> {
|
|||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final info = getBubbleInfo(
|
||||
context,
|
||||
widget.message,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
);
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: info.color,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: widget.info.color,
|
||||
borderRadius: widget.borderRadius,
|
||||
),
|
||||
child: IntrinsicWidth(
|
||||
child: Column(
|
||||
|
|
|
|||
|
|
@ -4,13 +4,19 @@ import 'package:twonly/src/model/protobuf/client/generated/data.pb.dart';
|
|||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/visual/elements/better_text.element.dart';
|
||||
|
||||
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/common.dart';
|
||||
|
||||
class ChatFlameRestoredEntry extends StatelessWidget {
|
||||
const ChatFlameRestoredEntry({
|
||||
required this.message,
|
||||
required this.borderRadius,
|
||||
required this.info,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Message message;
|
||||
final BorderRadiusGeometry borderRadius;
|
||||
final BubbleInfo info;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -34,10 +40,10 @@ class ChatFlameRestoredEntry extends StatelessWidget {
|
|||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.orange,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: BetterText(
|
||||
text: context.lang.chatEntryFlameRestored(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:twonly/locator.dart';
|
|||
import 'package:twonly/src/database/daos/contacts.dao.dart';
|
||||
import 'package:twonly/src/database/daos/user_discovery.dao.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/services/api/messages.api.dart';
|
||||
import 'package:twonly/src/services/api/utils.api.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
|
@ -82,6 +83,87 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _askFriends(
|
||||
BuildContext context,
|
||||
UserDiscoveryAnnouncedUser user,
|
||||
List<(Contact, DateTime?)> friends,
|
||||
) async {
|
||||
Log.info('Asking friends about user: ${user.announcedUserId}');
|
||||
final selectedFriends = <int>{};
|
||||
final username = user.username ?? '';
|
||||
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
return AlertDialog(
|
||||
title: Text(context.lang.askFriendsDialogTitle(username)),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(context.lang.askFriendsDialogDescription),
|
||||
const SizedBox(height: 10),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: friends.map((f) {
|
||||
final contact = f.$1;
|
||||
final isSelected =
|
||||
selectedFriends.contains(contact.userId);
|
||||
return CheckboxListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(contact.displayName ?? contact.username),
|
||||
value: isSelected,
|
||||
onChanged: (val) {
|
||||
setState(() {
|
||||
if (val == true) {
|
||||
selectedFriends.add(contact.userId);
|
||||
} else {
|
||||
selectedFriends.remove(contact.userId);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: Text(context.lang.askFriendsDialogCancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: selectedFriends.isEmpty
|
||||
? null
|
||||
: () => Navigator.pop(context, true),
|
||||
child: Text(context.lang.askFriendsDialogConfirm),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (result == true && selectedFriends.isNotEmpty) {
|
||||
for (final contactId in selectedFriends) {
|
||||
await insertAndSendAskAboutUserMessage(contactId, user.announcedUserId);
|
||||
}
|
||||
|
||||
await twonlyDB.userDiscoveryDao.updateAnnouncedUser(
|
||||
user.announcedUserId,
|
||||
const UserDiscoveryAnnouncedUsersCompanion(
|
||||
wasAskedFriends: Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (announcedUsers.isEmpty) return Container();
|
||||
|
|
@ -99,12 +181,31 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
|
||||
final friendsList = buildFriendsListText(context, friends);
|
||||
|
||||
return ListTile(
|
||||
return Padding(
|
||||
key: ValueKey(user.announcedUserId),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(substringBy(user.username!, 25)),
|
||||
subtitle: StreamBuilder(
|
||||
stream: twonlyDB.groupsDao.watchNonDirectGroupsForMember(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
const AvatarIcon(
|
||||
fontSize: 17,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
substringBy(user.username!, 25),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
StreamBuilder<List<Group>>(
|
||||
stream: twonlyDB.groupsDao
|
||||
.watchNonDirectGroupsForMember(
|
||||
user.announcedUserId,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
|
|
@ -114,7 +215,9 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
context,
|
||||
context.lang.friendSuggestionsGroupMemberIn(
|
||||
joinWithAnd(
|
||||
snapshot.data!.map((g) => '*${g.groupName}*').toList(),
|
||||
snapshot.data!
|
||||
.map((g) => '*${g.groupName}*')
|
||||
.toList(),
|
||||
context.lang.andWord,
|
||||
),
|
||||
),
|
||||
|
|
@ -123,29 +226,80 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
return RichText(
|
||||
text: TextSpan(
|
||||
children: text,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: context.color.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
leading: const AvatarIcon(
|
||||
fontSize: 17,
|
||||
],
|
||||
),
|
||||
trailing: Row(
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (!user.wasAskedFriends) ...[
|
||||
SizedBox(
|
||||
height: 28,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8,
|
||||
left: 4,
|
||||
),
|
||||
).merge(secondaryGreyButtonStyle(context)),
|
||||
onPressed: () => _askFriends(context, user, friends),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.circleQuestion,
|
||||
size: 12,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
context.lang.friendSuggestionsAskFriend,
|
||||
style: const TextStyle(fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
SizedBox(
|
||||
height: 26,
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.only(right: 8, left: 4),
|
||||
padding: const EdgeInsets.only(
|
||||
right: 8,
|
||||
left: 4,
|
||||
),
|
||||
).merge(secondaryGreyButtonStyle(context)),
|
||||
onPressed: () =>
|
||||
_requestAnnouncedUser(context, user),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
child: FaIcon(FontAwesomeIcons.userPlus, size: 12),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
),
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.userPlus,
|
||||
size: 12,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
context.lang.friendSuggestionsRequest,
|
||||
|
|
@ -153,9 +307,10 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => _requestAnnouncedUser(context, user),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
|
|
@ -167,6 +322,8 @@ class FriendSuggestionsComp extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/locator.dart';
|
||||
import 'package:twonly/src/services/user.service.dart';
|
||||
|
|
@ -30,7 +31,7 @@ class UserDiscoverySetupState {
|
|||
this.sharePromotion = true,
|
||||
this.isManualApprovalEnabled = false,
|
||||
this.threshold = 3,
|
||||
this.requiredSendImages = 4,
|
||||
this.requiredSendImages = kReleaseMode ? 4 : 0,
|
||||
});
|
||||
|
||||
bool wasChanged = false;
|
||||
|
|
@ -122,7 +123,6 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// First description text (centered, no card/title/icon)
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: formattedText(
|
||||
|
|
@ -278,7 +278,6 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Checkboxes / settings Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
|
|
@ -370,7 +369,6 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// First description text (centered, no card/title/icon)
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: formattedText(
|
||||
|
|
@ -542,7 +540,6 @@ class UserDiscoverySetupComp extends StatelessWidget {
|
|||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Checkboxes / settings Card
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import 'schema_v13.dart' as v13;
|
|||
import 'schema_v14.dart' as v14;
|
||||
import 'schema_v15.dart' as v15;
|
||||
import 'schema_v16.dart' as v16;
|
||||
import 'schema_v17.dart' as v17;
|
||||
|
||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||
@override
|
||||
|
|
@ -57,6 +58,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||
return v15.DatabaseAtV15(db);
|
||||
case 16:
|
||||
return v16.DatabaseAtV16(db);
|
||||
case 17:
|
||||
return v17.DatabaseAtV17(db);
|
||||
default:
|
||||
throw MissingSchemaException(version, versions);
|
||||
}
|
||||
|
|
@ -79,5 +82,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
];
|
||||
}
|
||||
|
|
|
|||
10624
test/drift/twonly_db/generated/schema_v17.dart
Normal file
10624
test/drift/twonly_db/generated/schema_v17.dart
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue