mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
change group name possible #227
This commit is contained in:
parent
9eea69b3dd
commit
30086c2475
18 changed files with 923 additions and 115 deletions
|
|
@ -60,6 +60,13 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
await into(groupHistories).insert(insertAction);
|
||||
}
|
||||
|
||||
Stream<List<GroupHistory>> watchGroupActions(String groupId) {
|
||||
return (select(groupHistories)
|
||||
..where((t) => t.groupId.equals(groupId))
|
||||
..orderBy([(t) => OrderingTerm.asc(t.actionAt)]))
|
||||
.watch();
|
||||
}
|
||||
|
||||
Future<void> updateMember(
|
||||
String groupId,
|
||||
int contactId,
|
||||
|
|
@ -139,6 +146,19 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
|||
return query.map((row) => row.readTable(contacts)).watch();
|
||||
}
|
||||
|
||||
Stream<List<(Contact, GroupMember)>> watchGroupMembers(String groupId) {
|
||||
final query =
|
||||
(select(groupMembers)..where((t) => t.groupId.equals(groupId))).join([
|
||||
leftOuterJoin(
|
||||
contacts,
|
||||
contacts.userId.equalsExp(groupMembers.contactId),
|
||||
),
|
||||
]);
|
||||
return query
|
||||
.map((row) => (row.readTable(contacts), row.readTable(groupMembers)))
|
||||
.watch();
|
||||
}
|
||||
|
||||
Stream<List<Group>> watchGroups() {
|
||||
return select(groups).watch();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ class GroupHistories extends Table {
|
|||
TextColumn get groupId =>
|
||||
text().references(Groups, #groupId, onDelete: KeyAction.cascade)();
|
||||
|
||||
IntColumn get contactId =>
|
||||
integer().nullable().references(Contacts, #userId)();
|
||||
|
||||
IntColumn get affectedContactId =>
|
||||
integer().nullable().references(Contacts, #userId)();
|
||||
|
||||
|
|
@ -95,3 +98,10 @@ class GroupHistories extends Table {
|
|||
@override
|
||||
Set<Column> get primaryKey => {groupHistoryId};
|
||||
}
|
||||
|
||||
GroupActionType? groupActionTypeFromString(String name) {
|
||||
for (final v in GroupActionType.values) {
|
||||
if (v.name == name) return v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6880,6 +6880,15 @@ class $GroupHistoriesTable extends GroupHistories
|
|||
requiredDuringInsert: true,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES "groups" (group_id) ON DELETE CASCADE'));
|
||||
static const VerificationMeta _contactIdMeta =
|
||||
const VerificationMeta('contactId');
|
||||
@override
|
||||
late final GeneratedColumn<int> contactId = GeneratedColumn<int>(
|
||||
'contact_id', aliasedName, true,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultConstraints:
|
||||
GeneratedColumn.constraintIsAlways('REFERENCES contacts (user_id)'));
|
||||
static const VerificationMeta _affectedContactIdMeta =
|
||||
const VerificationMeta('affectedContactId');
|
||||
@override
|
||||
|
|
@ -6918,6 +6927,7 @@ class $GroupHistoriesTable extends GroupHistories
|
|||
List<GeneratedColumn> get $columns => [
|
||||
groupHistoryId,
|
||||
groupId,
|
||||
contactId,
|
||||
affectedContactId,
|
||||
oldGroupName,
|
||||
newGroupName,
|
||||
|
|
@ -6948,6 +6958,10 @@ class $GroupHistoriesTable extends GroupHistories
|
|||
} else if (isInserting) {
|
||||
context.missing(_groupIdMeta);
|
||||
}
|
||||
if (data.containsKey('contact_id')) {
|
||||
context.handle(_contactIdMeta,
|
||||
contactId.isAcceptableOrUnknown(data['contact_id']!, _contactIdMeta));
|
||||
}
|
||||
if (data.containsKey('affected_contact_id')) {
|
||||
context.handle(
|
||||
_affectedContactIdMeta,
|
||||
|
|
@ -6983,6 +6997,8 @@ class $GroupHistoriesTable extends GroupHistories
|
|||
DriftSqlType.string, data['${effectivePrefix}group_history_id'])!,
|
||||
groupId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}group_id'])!,
|
||||
contactId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.int, data['${effectivePrefix}contact_id']),
|
||||
affectedContactId: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.int, data['${effectivePrefix}affected_contact_id']),
|
||||
oldGroupName: attachedDatabase.typeMapping
|
||||
|
|
@ -7009,6 +7025,7 @@ class $GroupHistoriesTable extends GroupHistories
|
|||
class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||
final String groupHistoryId;
|
||||
final String groupId;
|
||||
final int? contactId;
|
||||
final int? affectedContactId;
|
||||
final String? oldGroupName;
|
||||
final String? newGroupName;
|
||||
|
|
@ -7017,6 +7034,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
const GroupHistory(
|
||||
{required this.groupHistoryId,
|
||||
required this.groupId,
|
||||
this.contactId,
|
||||
this.affectedContactId,
|
||||
this.oldGroupName,
|
||||
this.newGroupName,
|
||||
|
|
@ -7027,6 +7045,9 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
final map = <String, Expression>{};
|
||||
map['group_history_id'] = Variable<String>(groupHistoryId);
|
||||
map['group_id'] = Variable<String>(groupId);
|
||||
if (!nullToAbsent || contactId != null) {
|
||||
map['contact_id'] = Variable<int>(contactId);
|
||||
}
|
||||
if (!nullToAbsent || affectedContactId != null) {
|
||||
map['affected_contact_id'] = Variable<int>(affectedContactId);
|
||||
}
|
||||
|
|
@ -7048,6 +7069,9 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
return GroupHistoriesCompanion(
|
||||
groupHistoryId: Value(groupHistoryId),
|
||||
groupId: Value(groupId),
|
||||
contactId: contactId == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(contactId),
|
||||
affectedContactId: affectedContactId == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(affectedContactId),
|
||||
|
|
@ -7068,6 +7092,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
return GroupHistory(
|
||||
groupHistoryId: serializer.fromJson<String>(json['groupHistoryId']),
|
||||
groupId: serializer.fromJson<String>(json['groupId']),
|
||||
contactId: serializer.fromJson<int?>(json['contactId']),
|
||||
affectedContactId: serializer.fromJson<int?>(json['affectedContactId']),
|
||||
oldGroupName: serializer.fromJson<String?>(json['oldGroupName']),
|
||||
newGroupName: serializer.fromJson<String?>(json['newGroupName']),
|
||||
|
|
@ -7082,6 +7107,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
return <String, dynamic>{
|
||||
'groupHistoryId': serializer.toJson<String>(groupHistoryId),
|
||||
'groupId': serializer.toJson<String>(groupId),
|
||||
'contactId': serializer.toJson<int?>(contactId),
|
||||
'affectedContactId': serializer.toJson<int?>(affectedContactId),
|
||||
'oldGroupName': serializer.toJson<String?>(oldGroupName),
|
||||
'newGroupName': serializer.toJson<String?>(newGroupName),
|
||||
|
|
@ -7094,6 +7120,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
GroupHistory copyWith(
|
||||
{String? groupHistoryId,
|
||||
String? groupId,
|
||||
Value<int?> contactId = const Value.absent(),
|
||||
Value<int?> affectedContactId = const Value.absent(),
|
||||
Value<String?> oldGroupName = const Value.absent(),
|
||||
Value<String?> newGroupName = const Value.absent(),
|
||||
|
|
@ -7102,6 +7129,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
GroupHistory(
|
||||
groupHistoryId: groupHistoryId ?? this.groupHistoryId,
|
||||
groupId: groupId ?? this.groupId,
|
||||
contactId: contactId.present ? contactId.value : this.contactId,
|
||||
affectedContactId: affectedContactId.present
|
||||
? affectedContactId.value
|
||||
: this.affectedContactId,
|
||||
|
|
@ -7118,6 +7146,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
? data.groupHistoryId.value
|
||||
: this.groupHistoryId,
|
||||
groupId: data.groupId.present ? data.groupId.value : this.groupId,
|
||||
contactId: data.contactId.present ? data.contactId.value : this.contactId,
|
||||
affectedContactId: data.affectedContactId.present
|
||||
? data.affectedContactId.value
|
||||
: this.affectedContactId,
|
||||
|
|
@ -7137,6 +7166,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
return (StringBuffer('GroupHistory(')
|
||||
..write('groupHistoryId: $groupHistoryId, ')
|
||||
..write('groupId: $groupId, ')
|
||||
..write('contactId: $contactId, ')
|
||||
..write('affectedContactId: $affectedContactId, ')
|
||||
..write('oldGroupName: $oldGroupName, ')
|
||||
..write('newGroupName: $newGroupName, ')
|
||||
|
|
@ -7147,14 +7177,15 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(groupHistoryId, groupId, affectedContactId,
|
||||
oldGroupName, newGroupName, type, actionAt);
|
||||
int get hashCode => Object.hash(groupHistoryId, groupId, contactId,
|
||||
affectedContactId, oldGroupName, newGroupName, type, actionAt);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is GroupHistory &&
|
||||
other.groupHistoryId == this.groupHistoryId &&
|
||||
other.groupId == this.groupId &&
|
||||
other.contactId == this.contactId &&
|
||||
other.affectedContactId == this.affectedContactId &&
|
||||
other.oldGroupName == this.oldGroupName &&
|
||||
other.newGroupName == this.newGroupName &&
|
||||
|
|
@ -7165,6 +7196,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
|||
class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||
final Value<String> groupHistoryId;
|
||||
final Value<String> groupId;
|
||||
final Value<int?> contactId;
|
||||
final Value<int?> affectedContactId;
|
||||
final Value<String?> oldGroupName;
|
||||
final Value<String?> newGroupName;
|
||||
|
|
@ -7174,6 +7206,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
|||
const GroupHistoriesCompanion({
|
||||
this.groupHistoryId = const Value.absent(),
|
||||
this.groupId = const Value.absent(),
|
||||
this.contactId = const Value.absent(),
|
||||
this.affectedContactId = const Value.absent(),
|
||||
this.oldGroupName = const Value.absent(),
|
||||
this.newGroupName = const Value.absent(),
|
||||
|
|
@ -7184,6 +7217,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
|||
GroupHistoriesCompanion.insert({
|
||||
required String groupHistoryId,
|
||||
required String groupId,
|
||||
this.contactId = const Value.absent(),
|
||||
this.affectedContactId = const Value.absent(),
|
||||
this.oldGroupName = const Value.absent(),
|
||||
this.newGroupName = const Value.absent(),
|
||||
|
|
@ -7196,6 +7230,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
|||
static Insertable<GroupHistory> custom({
|
||||
Expression<String>? groupHistoryId,
|
||||
Expression<String>? groupId,
|
||||
Expression<int>? contactId,
|
||||
Expression<int>? affectedContactId,
|
||||
Expression<String>? oldGroupName,
|
||||
Expression<String>? newGroupName,
|
||||
|
|
@ -7206,6 +7241,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
|||
return RawValuesInsertable({
|
||||
if (groupHistoryId != null) 'group_history_id': groupHistoryId,
|
||||
if (groupId != null) 'group_id': groupId,
|
||||
if (contactId != null) 'contact_id': contactId,
|
||||
if (affectedContactId != null) 'affected_contact_id': affectedContactId,
|
||||
if (oldGroupName != null) 'old_group_name': oldGroupName,
|
||||
if (newGroupName != null) 'new_group_name': newGroupName,
|
||||
|
|
@ -7218,6 +7254,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
|||
GroupHistoriesCompanion copyWith(
|
||||
{Value<String>? groupHistoryId,
|
||||
Value<String>? groupId,
|
||||
Value<int?>? contactId,
|
||||
Value<int?>? affectedContactId,
|
||||
Value<String?>? oldGroupName,
|
||||
Value<String?>? newGroupName,
|
||||
|
|
@ -7227,6 +7264,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
|||
return GroupHistoriesCompanion(
|
||||
groupHistoryId: groupHistoryId ?? this.groupHistoryId,
|
||||
groupId: groupId ?? this.groupId,
|
||||
contactId: contactId ?? this.contactId,
|
||||
affectedContactId: affectedContactId ?? this.affectedContactId,
|
||||
oldGroupName: oldGroupName ?? this.oldGroupName,
|
||||
newGroupName: newGroupName ?? this.newGroupName,
|
||||
|
|
@ -7245,6 +7283,9 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
|||
if (groupId.present) {
|
||||
map['group_id'] = Variable<String>(groupId.value);
|
||||
}
|
||||
if (contactId.present) {
|
||||
map['contact_id'] = Variable<int>(contactId.value);
|
||||
}
|
||||
if (affectedContactId.present) {
|
||||
map['affected_contact_id'] = Variable<int>(affectedContactId.value);
|
||||
}
|
||||
|
|
@ -7272,6 +7313,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
|||
return (StringBuffer('GroupHistoriesCompanion(')
|
||||
..write('groupHistoryId: $groupHistoryId, ')
|
||||
..write('groupId: $groupId, ')
|
||||
..write('contactId: $contactId, ')
|
||||
..write('affectedContactId: $affectedContactId, ')
|
||||
..write('oldGroupName: $oldGroupName, ')
|
||||
..write('newGroupName: $newGroupName, ')
|
||||
|
|
@ -7568,22 +7610,6 @@ final class $$ContactsTableReferences
|
|||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: cache));
|
||||
}
|
||||
|
||||
static MultiTypedResultKey<$GroupHistoriesTable, List<GroupHistory>>
|
||||
_groupHistoriesRefsTable(_$TwonlyDB db) =>
|
||||
MultiTypedResultKey.fromTable(db.groupHistories,
|
||||
aliasName: $_aliasNameGenerator(
|
||||
db.contacts.userId, db.groupHistories.affectedContactId));
|
||||
|
||||
$$GroupHistoriesTableProcessedTableManager get groupHistoriesRefs {
|
||||
final manager = $$GroupHistoriesTableTableManager($_db, $_db.groupHistories)
|
||||
.filter((f) => f.affectedContactId.userId
|
||||
.sqlEquals($_itemColumn<int>('user_id')!));
|
||||
|
||||
final cache = $_typedResult.readTableOrNull(_groupHistoriesRefsTable($_db));
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: cache));
|
||||
}
|
||||
}
|
||||
|
||||
class $$ContactsTableFilterComposer
|
||||
|
|
@ -7766,27 +7792,6 @@ class $$ContactsTableFilterComposer
|
|||
));
|
||||
return f(composer);
|
||||
}
|
||||
|
||||
Expression<bool> groupHistoriesRefs(
|
||||
Expression<bool> Function($$GroupHistoriesTableFilterComposer f) f) {
|
||||
final $$GroupHistoriesTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.userId,
|
||||
referencedTable: $db.groupHistories,
|
||||
getReferencedColumn: (t) => t.affectedContactId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$$GroupHistoriesTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.groupHistories,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return f(composer);
|
||||
}
|
||||
}
|
||||
|
||||
class $$ContactsTableOrderingComposer
|
||||
|
|
@ -8020,27 +8025,6 @@ class $$ContactsTableAnnotationComposer
|
|||
));
|
||||
return f(composer);
|
||||
}
|
||||
|
||||
Expression<T> groupHistoriesRefs<T extends Object>(
|
||||
Expression<T> Function($$GroupHistoriesTableAnnotationComposer a) f) {
|
||||
final $$GroupHistoriesTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.userId,
|
||||
referencedTable: $db.groupHistories,
|
||||
getReferencedColumn: (t) => t.affectedContactId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$$GroupHistoriesTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.groupHistories,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return f(composer);
|
||||
}
|
||||
}
|
||||
|
||||
class $$ContactsTableTableManager extends RootTableManager<
|
||||
|
|
@ -8060,8 +8044,7 @@ class $$ContactsTableTableManager extends RootTableManager<
|
|||
bool groupMembersRefs,
|
||||
bool receiptsRefs,
|
||||
bool signalContactPreKeysRefs,
|
||||
bool signalContactSignedPreKeysRefs,
|
||||
bool groupHistoriesRefs})> {
|
||||
bool signalContactSignedPreKeysRefs})> {
|
||||
$$ContactsTableTableManager(_$TwonlyDB db, $ContactsTable table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
|
|
@ -8142,8 +8125,7 @@ class $$ContactsTableTableManager extends RootTableManager<
|
|||
groupMembersRefs = false,
|
||||
receiptsRefs = false,
|
||||
signalContactPreKeysRefs = false,
|
||||
signalContactSignedPreKeysRefs = false,
|
||||
groupHistoriesRefs = false}) {
|
||||
signalContactSignedPreKeysRefs = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [
|
||||
|
|
@ -8153,8 +8135,7 @@ class $$ContactsTableTableManager extends RootTableManager<
|
|||
if (receiptsRefs) db.receipts,
|
||||
if (signalContactPreKeysRefs) db.signalContactPreKeys,
|
||||
if (signalContactSignedPreKeysRefs)
|
||||
db.signalContactSignedPreKeys,
|
||||
if (groupHistoriesRefs) db.groupHistories
|
||||
db.signalContactSignedPreKeys
|
||||
],
|
||||
addJoins: null,
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
|
|
@ -8234,19 +8215,6 @@ class $$ContactsTableTableManager extends RootTableManager<
|
|||
referencedItemsForCurrentItem:
|
||||
(item, referencedItems) => referencedItems
|
||||
.where((e) => e.contactId == item.userId),
|
||||
typedResults: items),
|
||||
if (groupHistoriesRefs)
|
||||
await $_getPrefetchedData<Contact, $ContactsTable,
|
||||
GroupHistory>(
|
||||
currentTable: table,
|
||||
referencedTable: $$ContactsTableReferences
|
||||
._groupHistoriesRefsTable(db),
|
||||
managerFromTypedResult: (p0) =>
|
||||
$$ContactsTableReferences(db, table, p0)
|
||||
.groupHistoriesRefs,
|
||||
referencedItemsForCurrentItem:
|
||||
(item, referencedItems) => referencedItems.where(
|
||||
(e) => e.affectedContactId == item.userId),
|
||||
typedResults: items)
|
||||
];
|
||||
},
|
||||
|
|
@ -8272,8 +8240,7 @@ typedef $$ContactsTableProcessedTableManager = ProcessedTableManager<
|
|||
bool groupMembersRefs,
|
||||
bool receiptsRefs,
|
||||
bool signalContactPreKeysRefs,
|
||||
bool signalContactSignedPreKeysRefs,
|
||||
bool groupHistoriesRefs})>;
|
||||
bool signalContactSignedPreKeysRefs})>;
|
||||
typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
||||
required String groupId,
|
||||
Value<bool> isGroupAdmin,
|
||||
|
|
@ -13213,6 +13180,7 @@ typedef $$GroupHistoriesTableCreateCompanionBuilder = GroupHistoriesCompanion
|
|||
Function({
|
||||
required String groupHistoryId,
|
||||
required String groupId,
|
||||
Value<int?> contactId,
|
||||
Value<int?> affectedContactId,
|
||||
Value<String?> oldGroupName,
|
||||
Value<String?> newGroupName,
|
||||
|
|
@ -13224,6 +13192,7 @@ typedef $$GroupHistoriesTableUpdateCompanionBuilder = GroupHistoriesCompanion
|
|||
Function({
|
||||
Value<String> groupHistoryId,
|
||||
Value<String> groupId,
|
||||
Value<int?> contactId,
|
||||
Value<int?> affectedContactId,
|
||||
Value<String?> oldGroupName,
|
||||
Value<String?> newGroupName,
|
||||
|
|
@ -13251,6 +13220,21 @@ final class $$GroupHistoriesTableReferences
|
|||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
|
||||
static $ContactsTable _contactIdTable(_$TwonlyDB db) =>
|
||||
db.contacts.createAlias($_aliasNameGenerator(
|
||||
db.groupHistories.contactId, db.contacts.userId));
|
||||
|
||||
$$ContactsTableProcessedTableManager? get contactId {
|
||||
final $_column = $_itemColumn<int>('contact_id');
|
||||
if ($_column == null) return null;
|
||||
final manager = $$ContactsTableTableManager($_db, $_db.contacts)
|
||||
.filter((f) => f.userId.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_contactIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]));
|
||||
}
|
||||
|
||||
static $ContactsTable _affectedContactIdTable(_$TwonlyDB db) =>
|
||||
db.contacts.createAlias($_aliasNameGenerator(
|
||||
db.groupHistories.affectedContactId, db.contacts.userId));
|
||||
|
|
@ -13314,6 +13298,26 @@ class $$GroupHistoriesTableFilterComposer
|
|||
return composer;
|
||||
}
|
||||
|
||||
$$ContactsTableFilterComposer get contactId {
|
||||
final $$ContactsTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.contactId,
|
||||
referencedTable: $db.contacts,
|
||||
getReferencedColumn: (t) => t.userId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$$ContactsTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.contacts,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
$$ContactsTableFilterComposer get affectedContactId {
|
||||
final $$ContactsTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
|
|
@ -13382,6 +13386,26 @@ class $$GroupHistoriesTableOrderingComposer
|
|||
return composer;
|
||||
}
|
||||
|
||||
$$ContactsTableOrderingComposer get contactId {
|
||||
final $$ContactsTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.contactId,
|
||||
referencedTable: $db.contacts,
|
||||
getReferencedColumn: (t) => t.userId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$$ContactsTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: $db.contacts,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
$$ContactsTableOrderingComposer get affectedContactId {
|
||||
final $$ContactsTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
|
|
@ -13447,6 +13471,26 @@ class $$GroupHistoriesTableAnnotationComposer
|
|||
return composer;
|
||||
}
|
||||
|
||||
$$ContactsTableAnnotationComposer get contactId {
|
||||
final $$ContactsTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.contactId,
|
||||
referencedTable: $db.contacts,
|
||||
getReferencedColumn: (t) => t.userId,
|
||||
builder: (joinBuilder,
|
||||
{$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer}) =>
|
||||
$$ContactsTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.contacts,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
));
|
||||
return composer;
|
||||
}
|
||||
|
||||
$$ContactsTableAnnotationComposer get affectedContactId {
|
||||
final $$ContactsTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
|
|
@ -13479,7 +13523,8 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
|||
$$GroupHistoriesTableUpdateCompanionBuilder,
|
||||
(GroupHistory, $$GroupHistoriesTableReferences),
|
||||
GroupHistory,
|
||||
PrefetchHooks Function({bool groupId, bool affectedContactId})> {
|
||||
PrefetchHooks Function(
|
||||
{bool groupId, bool contactId, bool affectedContactId})> {
|
||||
$$GroupHistoriesTableTableManager(_$TwonlyDB db, $GroupHistoriesTable table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
|
|
@ -13493,6 +13538,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
|||
updateCompanionCallback: ({
|
||||
Value<String> groupHistoryId = const Value.absent(),
|
||||
Value<String> groupId = const Value.absent(),
|
||||
Value<int?> contactId = const Value.absent(),
|
||||
Value<int?> affectedContactId = const Value.absent(),
|
||||
Value<String?> oldGroupName = const Value.absent(),
|
||||
Value<String?> newGroupName = const Value.absent(),
|
||||
|
|
@ -13503,6 +13549,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
|||
GroupHistoriesCompanion(
|
||||
groupHistoryId: groupHistoryId,
|
||||
groupId: groupId,
|
||||
contactId: contactId,
|
||||
affectedContactId: affectedContactId,
|
||||
oldGroupName: oldGroupName,
|
||||
newGroupName: newGroupName,
|
||||
|
|
@ -13513,6 +13560,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
|||
createCompanionCallback: ({
|
||||
required String groupHistoryId,
|
||||
required String groupId,
|
||||
Value<int?> contactId = const Value.absent(),
|
||||
Value<int?> affectedContactId = const Value.absent(),
|
||||
Value<String?> oldGroupName = const Value.absent(),
|
||||
Value<String?> newGroupName = const Value.absent(),
|
||||
|
|
@ -13523,6 +13571,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
|||
GroupHistoriesCompanion.insert(
|
||||
groupHistoryId: groupHistoryId,
|
||||
groupId: groupId,
|
||||
contactId: contactId,
|
||||
affectedContactId: affectedContactId,
|
||||
oldGroupName: oldGroupName,
|
||||
newGroupName: newGroupName,
|
||||
|
|
@ -13537,7 +13586,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
|||
))
|
||||
.toList(),
|
||||
prefetchHooksCallback: (
|
||||
{groupId = false, affectedContactId = false}) {
|
||||
{groupId = false, contactId = false, affectedContactId = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [],
|
||||
|
|
@ -13565,6 +13614,17 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
|||
.groupId,
|
||||
) as T;
|
||||
}
|
||||
if (contactId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.contactId,
|
||||
referencedTable:
|
||||
$$GroupHistoriesTableReferences._contactIdTable(db),
|
||||
referencedColumn: $$GroupHistoriesTableReferences
|
||||
._contactIdTable(db)
|
||||
.userId,
|
||||
) as T;
|
||||
}
|
||||
if (affectedContactId) {
|
||||
state = state.withJoin(
|
||||
currentTable: table,
|
||||
|
|
@ -13598,7 +13658,8 @@ typedef $$GroupHistoriesTableProcessedTableManager = ProcessedTableManager<
|
|||
$$GroupHistoriesTableUpdateCompanionBuilder,
|
||||
(GroupHistory, $$GroupHistoriesTableReferences),
|
||||
GroupHistory,
|
||||
PrefetchHooks Function({bool groupId, bool affectedContactId})>;
|
||||
PrefetchHooks Function(
|
||||
{bool groupId, bool contactId, bool affectedContactId})>;
|
||||
|
||||
class $TwonlyDBManager {
|
||||
final _$TwonlyDB _db;
|
||||
|
|
|
|||
|
|
@ -365,5 +365,12 @@
|
|||
"selectGroupName": "Gruppennamen wählen",
|
||||
"groupNameInput": "Gruppennamen",
|
||||
"groupMembers": "Mitglieder",
|
||||
"createGroup": "Gruppe erstellen"
|
||||
"createGroup": "Gruppe erstellen",
|
||||
"addMember": "Mitglied hinzufügen",
|
||||
"leaveGroup": "Gruppe verlassen",
|
||||
"createContactRequest": "Kontaktanfrage erstellen",
|
||||
"makeAdmin": "Zum Admin machen",
|
||||
"removeAdmin": "Als Admin entfernen",
|
||||
"removeFromGroup": "Aus Gruppe entfernen",
|
||||
"admin": "Admin"
|
||||
}
|
||||
|
|
@ -521,5 +521,12 @@
|
|||
"selectGroupName": "Select group name",
|
||||
"groupNameInput": "Group name",
|
||||
"groupMembers": "Members",
|
||||
"createGroup": "Create group"
|
||||
"addMember": "Add member",
|
||||
"createGroup": "Create group",
|
||||
"leaveGroup": "Leave group",
|
||||
"createContactRequest": "Create contact request",
|
||||
"makeAdmin": "Make admin",
|
||||
"removeAdmin": "Remove as admin",
|
||||
"removeFromGroup": "Remove from group",
|
||||
"admin": "Admin"
|
||||
}
|
||||
|
|
@ -2228,11 +2228,53 @@ abstract class AppLocalizations {
|
|||
/// **'Members'**
|
||||
String get groupMembers;
|
||||
|
||||
/// No description provided for @addMember.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add member'**
|
||||
String get addMember;
|
||||
|
||||
/// No description provided for @createGroup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create group'**
|
||||
String get createGroup;
|
||||
|
||||
/// No description provided for @leaveGroup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Leave group'**
|
||||
String get leaveGroup;
|
||||
|
||||
/// No description provided for @createContactRequest.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create contact request'**
|
||||
String get createContactRequest;
|
||||
|
||||
/// No description provided for @makeAdmin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Make admin'**
|
||||
String get makeAdmin;
|
||||
|
||||
/// No description provided for @removeAdmin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Remove as admin'**
|
||||
String get removeAdmin;
|
||||
|
||||
/// No description provided for @removeFromGroup.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Remove from group'**
|
||||
String get removeFromGroup;
|
||||
|
||||
/// No description provided for @admin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Admin'**
|
||||
String get admin;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -1181,6 +1181,27 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get groupMembers => 'Mitglieder';
|
||||
|
||||
@override
|
||||
String get addMember => 'Mitglied hinzufügen';
|
||||
|
||||
@override
|
||||
String get createGroup => 'Gruppe erstellen';
|
||||
|
||||
@override
|
||||
String get leaveGroup => 'Gruppe verlassen';
|
||||
|
||||
@override
|
||||
String get createContactRequest => 'Kontaktanfrage erstellen';
|
||||
|
||||
@override
|
||||
String get makeAdmin => 'Zum Admin machen';
|
||||
|
||||
@override
|
||||
String get removeAdmin => 'Als Admin entfernen';
|
||||
|
||||
@override
|
||||
String get removeFromGroup => 'Aus Gruppe entfernen';
|
||||
|
||||
@override
|
||||
String get admin => 'Admin';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1174,6 +1174,27 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get groupMembers => 'Members';
|
||||
|
||||
@override
|
||||
String get addMember => 'Add member';
|
||||
|
||||
@override
|
||||
String get createGroup => 'Create group';
|
||||
|
||||
@override
|
||||
String get leaveGroup => 'Leave group';
|
||||
|
||||
@override
|
||||
String get createContactRequest => 'Create contact request';
|
||||
|
||||
@override
|
||||
String get makeAdmin => 'Make admin';
|
||||
|
||||
@override
|
||||
String get removeAdmin => 'Remove as admin';
|
||||
|
||||
@override
|
||||
String get removeFromGroup => 'Remove from group';
|
||||
|
||||
@override
|
||||
String get admin => 'Admin';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
|
|
@ -29,7 +28,7 @@ Future<void> handleGroupCreate(
|
|||
groupId: Value(groupId),
|
||||
stateVersionId: const Value(0),
|
||||
stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)),
|
||||
myGroupPrivateKey: Value(myGroupKey.getPrivateKey().serialize()),
|
||||
myGroupPrivateKey: Value(myGroupKey.serialize()),
|
||||
groupName: const Value(''),
|
||||
joinedGroup: const Value(false),
|
||||
),
|
||||
|
|
@ -81,7 +80,53 @@ Future<void> handleGroupUpdate(
|
|||
int fromUserId,
|
||||
String groupId,
|
||||
EncryptedContent_GroupUpdate update,
|
||||
) async {}
|
||||
) async {
|
||||
Log.info('Got group update for $groupId from $fromUserId');
|
||||
|
||||
final actionType = groupActionTypeFromString(update.groupActionType);
|
||||
if (actionType == null) {
|
||||
Log.error('Group action ${update.groupActionType} is unknown ignoring.');
|
||||
return;
|
||||
}
|
||||
|
||||
final group = (await twonlyDB.groupsDao.getGroup(groupId))!;
|
||||
|
||||
switch (actionType) {
|
||||
case GroupActionType.updatedGroupName:
|
||||
await twonlyDB.groupsDao.insertGroupAction(
|
||||
GroupHistoriesCompanion(
|
||||
groupId: Value(groupId),
|
||||
type: Value(actionType),
|
||||
oldGroupName: Value(group.groupName),
|
||||
newGroupName: Value(update.newGroupName),
|
||||
contactId: Value(fromUserId),
|
||||
),
|
||||
);
|
||||
case GroupActionType.removedMember:
|
||||
case GroupActionType.addMember:
|
||||
case GroupActionType.leftGroup:
|
||||
case GroupActionType.promoteToAdmin:
|
||||
case GroupActionType.demoteToMember:
|
||||
int? affectedContactId = update.affectedContactId.toInt();
|
||||
|
||||
if (affectedContactId == gUser.userId) {
|
||||
affectedContactId = null;
|
||||
}
|
||||
|
||||
await twonlyDB.groupsDao.insertGroupAction(
|
||||
GroupHistoriesCompanion(
|
||||
groupId: Value(groupId),
|
||||
type: Value(actionType),
|
||||
affectedContactId: Value(affectedContactId),
|
||||
contactId: Value(fromUserId),
|
||||
),
|
||||
);
|
||||
case GroupActionType.createdGroup:
|
||||
break;
|
||||
}
|
||||
|
||||
unawaited(fetchGroupState(group));
|
||||
}
|
||||
|
||||
Future<bool> handleGroupJoin(
|
||||
int fromUserId,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
|
|
@ -8,6 +9,8 @@ import 'package:fixnum/fixnum.dart';
|
|||
import 'package:hashlib/random.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:libsignal_protocol_dart/src/ecc/ed25519.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
|
@ -23,6 +26,10 @@ String getGroupStateUrl() {
|
|||
return 'http${apiService.apiSecure}://${apiService.apiHost}/api/group/state';
|
||||
}
|
||||
|
||||
String getGroupChallengeUrl() {
|
||||
return 'http${apiService.apiSecure}://${apiService.apiHost}/api/group/challenge';
|
||||
}
|
||||
|
||||
Future<bool> createNewGroup(String groupName, List<Contact> members) async {
|
||||
// First: Upload new State to the server.....
|
||||
// if (groupName) return;
|
||||
|
|
@ -90,7 +97,7 @@ Future<bool> createNewGroup(String groupName, List<Contact> members) async {
|
|||
isGroupAdmin: const Value(true),
|
||||
stateEncryptionKey: Value(stateEncryptionKey),
|
||||
stateVersionId: const Value(1),
|
||||
myGroupPrivateKey: Value(myGroupKey.getPrivateKey().serialize()),
|
||||
myGroupPrivateKey: Value(myGroupKey.serialize()),
|
||||
joinedGroup: const Value(true),
|
||||
),
|
||||
);
|
||||
|
|
@ -142,7 +149,7 @@ Future<void> fetchGroupStatesForUnjoinedGroups() async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> fetchGroupState(Group group) async {
|
||||
Future<(int, EncryptedGroupState)?> fetchGroupState(Group group) async {
|
||||
try {
|
||||
var isSuccess = true;
|
||||
|
||||
|
|
@ -156,7 +163,7 @@ Future<bool> fetchGroupState(Group group) async {
|
|||
Log.error(
|
||||
'Could not load group state. Got status code ${response.statusCode} from server.',
|
||||
);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
final groupStateServer = GroupState.fromBuffer(response.bodyBytes);
|
||||
|
|
@ -180,8 +187,10 @@ Future<bool> fetchGroupState(Group group) async {
|
|||
EncryptedGroupState.fromBuffer(encryptedGroupStateRaw);
|
||||
|
||||
if (group.stateVersionId >= groupStateServer.versionId.toInt()) {
|
||||
Log.error('Group ${group.groupId} has newest group state');
|
||||
return false;
|
||||
Log.info(
|
||||
'Group ${group.groupId} has already newest group state from the server!',
|
||||
);
|
||||
return (groupStateServer.versionId.toInt(), encryptedGroupState);
|
||||
}
|
||||
|
||||
final isGroupAdmin = encryptedGroupState.adminIds
|
||||
|
|
@ -204,6 +213,9 @@ Future<bool> fetchGroupState(Group group) async {
|
|||
|
||||
// First find and insert NEW members
|
||||
for (final memberId in encryptedGroupState.memberIds) {
|
||||
if (memberId == Int64(gUser.userId)) {
|
||||
continue;
|
||||
}
|
||||
if (currentGroupMembers.any((t) => t.contactId == memberId.toInt())) {
|
||||
// User is already in the database
|
||||
continue;
|
||||
|
|
@ -280,10 +292,10 @@ Future<bool> fetchGroupState(Group group) async {
|
|||
),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
return (groupStateServer.versionId.toInt(), encryptedGroupState);
|
||||
} catch (e) {
|
||||
Log.error(e);
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -304,3 +316,112 @@ Future<bool> addNewHiddenContact(int contactId) async {
|
|||
await createNewSignalSession(userData);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> updateGroupState(Group group, EncryptedGroupState state) async {
|
||||
final chacha20 = FlutterChacha20.poly1305Aead();
|
||||
final encryptionNonce = chacha20.newNonce();
|
||||
|
||||
final secretBox = await chacha20.encrypt(
|
||||
state.writeToBuffer(),
|
||||
secretKey: SecretKey(group.stateEncryptionKey!),
|
||||
nonce: encryptionNonce,
|
||||
);
|
||||
|
||||
final encryptedGroupState = EncryptedGroupStateEnvelop(
|
||||
nonce: encryptionNonce,
|
||||
encryptedGroupState: secretBox.cipherText,
|
||||
mac: secretBox.mac.bytes,
|
||||
);
|
||||
|
||||
{
|
||||
// Upload the group state, if this fails, the group can not be created.
|
||||
|
||||
final keyPair = IdentityKeyPair.fromSerialized(group.myGroupPrivateKey!);
|
||||
|
||||
final publicKey = uint8ListToHex(keyPair.getPublicKey().serialize());
|
||||
|
||||
final responseNonce = await http
|
||||
.get(
|
||||
Uri.parse('${getGroupChallengeUrl()}/$publicKey'),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (responseNonce.statusCode != 200) {
|
||||
Log.error(
|
||||
'Could not load nonce. Got status code ${responseNonce.statusCode} from server.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final updateTBS = UpdateGroupState_UpdateTBS(
|
||||
versionId: Int64(group.stateVersionId + 1),
|
||||
encryptedGroupState: encryptedGroupState.writeToBuffer(),
|
||||
publicKey: keyPair.getPublicKey().serialize(),
|
||||
nonce: responseNonce.bodyBytes,
|
||||
);
|
||||
|
||||
final random = getRandomUint8List(32);
|
||||
final signature = sign(
|
||||
keyPair.getPrivateKey().serialize(),
|
||||
updateTBS.writeToBuffer(),
|
||||
random,
|
||||
);
|
||||
|
||||
final newGroupState = UpdateGroupState(
|
||||
update: updateTBS,
|
||||
signature: signature,
|
||||
);
|
||||
|
||||
final response = await http
|
||||
.patch(
|
||||
Uri.parse(getGroupStateUrl()),
|
||||
body: newGroupState.writeToBuffer(),
|
||||
)
|
||||
.timeout(const Duration(seconds: 10));
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
Log.error(
|
||||
'Could not patch group state. Got status code ${response.statusCode} from server.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update database to the newest state
|
||||
return (await fetchGroupState(group)) != null;
|
||||
}
|
||||
|
||||
Future<bool> updateGroupeName(Group group, String groupName) async {
|
||||
// ensure the latest state is used
|
||||
final currentState = await fetchGroupState(group);
|
||||
if (currentState == null) return false;
|
||||
final (versionId, state) = currentState;
|
||||
|
||||
state.groupName = groupName;
|
||||
|
||||
// send new state to the server
|
||||
if (!await updateGroupState(group, state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await sendCipherTextToGroup(
|
||||
group.groupId,
|
||||
EncryptedContent(
|
||||
groupUpdate: EncryptedContent_GroupUpdate(
|
||||
groupActionType: GroupActionType.updatedGroupName.name,
|
||||
newGroupName: groupName,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await twonlyDB.groupsDao.insertGroupAction(
|
||||
GroupHistoriesCompanion(
|
||||
groupId: Value(group.groupId),
|
||||
type: const Value(GroupActionType.updatedGroupName),
|
||||
oldGroupName: Value(group.groupName),
|
||||
newGroupName: Value(groupName),
|
||||
),
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import 'package:twonly/src/views/chats/media_viewer.view.dart';
|
|||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||
import 'package:twonly/src/views/components/flame.dart';
|
||||
import 'package:twonly/src/views/components/group_context_menu.component.dart';
|
||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||
import 'package:twonly/src/views/groups/group.view.dart';
|
||||
|
||||
class GroupListItem extends StatefulWidget {
|
||||
const GroupListItem({
|
||||
|
|
@ -232,7 +234,27 @@ class _UserListItem extends State<GroupListItem> {
|
|||
),
|
||||
],
|
||||
),
|
||||
leading: AvatarIcon(group: widget.group),
|
||||
leading: GestureDetector(
|
||||
onTap: () async {
|
||||
Widget pushWidget = GroupView(widget.group);
|
||||
|
||||
if (widget.group.isDirectChat) {
|
||||
final contacts = await twonlyDB.groupsDao
|
||||
.getGroupContact(widget.group.groupId);
|
||||
pushWidget = ContactView(contacts.first.userId);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return pushWidget;
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AvatarIcon(group: widget.group),
|
||||
),
|
||||
trailing: IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:twonly/src/services/notifications/background.notifications.dart'
|
|||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages_components/chat_date_chip.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages_components/chat_group_action.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages_components/chat_list_entry.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||
|
|
@ -30,7 +31,12 @@ Color getMessageColor(Message message) {
|
|||
}
|
||||
|
||||
class ChatItem {
|
||||
const ChatItem._({this.message, this.date, this.lastOpenedPosition});
|
||||
const ChatItem._({
|
||||
this.message,
|
||||
this.date,
|
||||
this.lastOpenedPosition,
|
||||
this.groupAction,
|
||||
});
|
||||
factory ChatItem.date(DateTime date) {
|
||||
return ChatItem._(date: date);
|
||||
}
|
||||
|
|
@ -40,11 +46,16 @@ class ChatItem {
|
|||
factory ChatItem.lastOpenedPosition(List<Contact> contacts) {
|
||||
return ChatItem._(lastOpenedPosition: contacts);
|
||||
}
|
||||
factory ChatItem.groupAction(GroupHistory groupAction) {
|
||||
return ChatItem._(groupAction: groupAction);
|
||||
}
|
||||
final GroupHistory? groupAction;
|
||||
final Message? message;
|
||||
final DateTime? date;
|
||||
final List<Contact>? lastOpenedPosition;
|
||||
bool get isMessage => message != null;
|
||||
bool get isDate => date != null;
|
||||
bool get isGroupAction => groupAction != null;
|
||||
bool get isLastOpenedPosition => lastOpenedPosition != null;
|
||||
}
|
||||
|
||||
|
|
@ -65,12 +76,14 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
String currentInputText = '';
|
||||
late StreamSubscription<Group?> userSub;
|
||||
late StreamSubscription<List<Message>> messageSub;
|
||||
late StreamSubscription<List<GroupHistory>>? groupActionsSub;
|
||||
late StreamSubscription<Future<List<(Message, Contact)>>>?
|
||||
lastOpenedMessageByContactSub;
|
||||
|
||||
List<ChatItem> messages = [];
|
||||
List<Message> allMessages = [];
|
||||
List<(Message, Contact)> lastOpenedMessageByContact = [];
|
||||
List<GroupHistory> groupActions = [];
|
||||
List<MemoryItem> galleryItems = [];
|
||||
Message? quotesMessage;
|
||||
GlobalKey verifyShieldKey = GlobalKey();
|
||||
|
|
@ -97,6 +110,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
void dispose() {
|
||||
userSub.cancel();
|
||||
messageSub.cancel();
|
||||
groupActionsSub?.cancel();
|
||||
lastOpenedMessageByContactSub?.cancel();
|
||||
tutorial?.cancel();
|
||||
textFieldFocus.dispose();
|
||||
|
|
@ -121,7 +135,13 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
lastOpenedStream.listen((lastActionsFuture) async {
|
||||
final update = await lastActionsFuture;
|
||||
lastOpenedMessageByContact = update;
|
||||
await setMessages(allMessages, update);
|
||||
await setMessages(allMessages, update, groupActions);
|
||||
});
|
||||
|
||||
final actionsStream = twonlyDB.groupsDao.watchGroupActions(group.groupId);
|
||||
groupActionsSub = actionsStream.listen((update) async {
|
||||
groupActions = update;
|
||||
await setMessages(allMessages, lastOpenedMessageByContact, update);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +155,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
// return;
|
||||
}
|
||||
await protectMessageUpdating.protect(() async {
|
||||
await setMessages(update, lastOpenedMessageByContact);
|
||||
await setMessages(update, lastOpenedMessageByContact, groupActions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -143,6 +163,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
Future<void> setMessages(
|
||||
List<Message> newMessages,
|
||||
List<(Message, Contact)> lastOpenedMessageByContact,
|
||||
List<GroupHistory> groupActions,
|
||||
) async {
|
||||
await flutterLocalNotificationsPlugin.cancelAll();
|
||||
|
||||
|
|
@ -165,8 +186,20 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
}
|
||||
}
|
||||
var index = 0;
|
||||
var groupHistoryIndex = 0;
|
||||
|
||||
for (final msg in newMessages) {
|
||||
if (groupHistoryIndex < groupActions.length) {
|
||||
for (; groupHistoryIndex < groupActions.length; groupHistoryIndex++) {
|
||||
if (msg.createdAt.isAfter(groupActions[groupHistoryIndex].actionAt)) {
|
||||
chatItems
|
||||
.add(ChatItem.groupAction(groupActions[groupHistoryIndex]));
|
||||
// groupHistoryIndex++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
if (msg.type == MessageType.text &&
|
||||
msg.senderId != null &&
|
||||
|
|
@ -200,6 +233,11 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (groupHistoryIndex < groupActions.length) {
|
||||
for (var i = groupHistoryIndex; i < groupActions.length; i++) {
|
||||
chatItems.add(ChatItem.groupAction(groupActions[i]));
|
||||
}
|
||||
}
|
||||
|
||||
for (final contactId in openedMessages.keys) {
|
||||
await notifyContactAboutOpeningMessage(
|
||||
|
|
@ -262,9 +300,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
appBar: AppBar(
|
||||
title: GestureDetector(
|
||||
onTap: () async {
|
||||
if (widget.group.isDirectChat) {
|
||||
final member = await twonlyDB.groupsDao
|
||||
.getGroupMembers(widget.group.groupId);
|
||||
if (group.isDirectChat) {
|
||||
final member =
|
||||
await twonlyDB.groupsDao.getGroupMembers(group.groupId);
|
||||
if (!context.mounted) return;
|
||||
await Navigator.push(
|
||||
context,
|
||||
|
|
@ -279,7 +317,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return GroupView(widget.group);
|
||||
return GroupView(group);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
@ -298,7 +336,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
substringBy(widget.group.groupName, 20),
|
||||
substringBy(group.groupName, 20),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
VerifiedShield(key: verifyShieldKey, group: group),
|
||||
|
|
@ -342,6 +380,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
);
|
||||
}).toList(),
|
||||
);
|
||||
} else if (messages[i].isGroupAction) {
|
||||
return ChatGroupAction(action: messages[i].groupAction!);
|
||||
} else {
|
||||
final chatMessage = messages[i].message!;
|
||||
return Transform.translate(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/globals.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';
|
||||
|
||||
class ChatGroupAction extends StatefulWidget {
|
||||
const ChatGroupAction({
|
||||
required this.action,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final GroupHistory action;
|
||||
|
||||
@override
|
||||
State<ChatGroupAction> createState() => _ChatGroupActionState();
|
||||
}
|
||||
|
||||
class _ChatGroupActionState extends State<ChatGroupAction> {
|
||||
Contact? contact;
|
||||
Contact? affectedContact;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
initAsync();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
if (widget.action.contactId == null) return;
|
||||
contact =
|
||||
await twonlyDB.contactsDao.getContactById(widget.action.contactId!);
|
||||
|
||||
if (widget.action.affectedContactId == null) return;
|
||||
affectedContact = await twonlyDB.contactsDao
|
||||
.getContactById(widget.action.affectedContactId!);
|
||||
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var text = '';
|
||||
|
||||
if (widget.action.type == GroupActionType.updatedGroupName) {
|
||||
if (contact == null) {
|
||||
text =
|
||||
'You have changed the group name to "${widget.action.newGroupName}".';
|
||||
} else {
|
||||
text =
|
||||
'${getContactDisplayName(contact!)} has changed the group name to "${widget.action.newGroupName}".';
|
||||
}
|
||||
}
|
||||
|
||||
if (text == '') return Container();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Center(
|
||||
child: RichText(
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
children: [
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.pencil,
|
||||
size: 10,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
const WidgetSpan(child: SizedBox(width: 8)),
|
||||
TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -126,6 +126,7 @@ class _AvatarIconState extends State<AvatarIcon> {
|
|||
}
|
||||
|
||||
return Container(
|
||||
key: GlobalKey(),
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 2 * (widget.fontSize ?? 20),
|
||||
minWidth: 2 * (widget.fontSize ?? 20),
|
||||
|
|
|
|||
|
|
@ -3,16 +3,20 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||
|
||||
class BetterListTile extends StatelessWidget {
|
||||
const BetterListTile({
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.onTap,
|
||||
this.icon,
|
||||
this.leading,
|
||||
super.key,
|
||||
this.color,
|
||||
this.subtitle,
|
||||
this.trailing,
|
||||
this.iconSize = 20,
|
||||
this.padding,
|
||||
});
|
||||
final IconData icon;
|
||||
final IconData? icon;
|
||||
final Widget? leading;
|
||||
final Widget? trailing;
|
||||
final String text;
|
||||
final Widget? subtitle;
|
||||
final Color? color;
|
||||
|
|
@ -30,12 +34,15 @@ class BetterListTile extends StatelessWidget {
|
|||
left: 19,
|
||||
)
|
||||
: padding!,
|
||||
child: FaIcon(
|
||||
child: (leading != null)
|
||||
? leading
|
||||
: FaIcon(
|
||||
icon,
|
||||
size: iconSize,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
trailing: trailing,
|
||||
title: Text(
|
||||
text,
|
||||
style: TextStyle(color: color),
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class _ContextMenuState extends State<ContextMenu> {
|
|||
elevation: 1,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12), // corner radius
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
popUpAnimationStyle: const AnimationStyle(
|
||||
duration: Duration.zero,
|
||||
|
|
@ -56,7 +56,7 @@ class _ContextMenuState extends State<ContextMenu> {
|
|||
items: <PopupMenuEntry<int>>[
|
||||
...widget.items.map(
|
||||
(item) => PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: ListTile(
|
||||
title: Text(item.title),
|
||||
onTap: () async {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,18 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/globals.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/group.services.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||
import 'package:twonly/src/views/components/verified_shield.dart';
|
||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||
import 'package:twonly/src/views/groups/group_member.context.dart';
|
||||
import 'package:twonly/src/views/settings/profile/profile.view.dart';
|
||||
|
||||
class GroupView extends StatefulWidget {
|
||||
const GroupView(this.group, {super.key});
|
||||
|
|
@ -11,8 +24,211 @@ class GroupView extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _GroupViewState extends State<GroupView> {
|
||||
late Group group;
|
||||
|
||||
List<(Contact, GroupMember)> members = [];
|
||||
|
||||
late StreamSubscription<Group?> groupSub;
|
||||
late StreamSubscription<List<(Contact, GroupMember)>> membersSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
group = widget.group;
|
||||
initAsync();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
groupSub.cancel();
|
||||
membersSub.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
final groupStream = twonlyDB.groupsDao.watchGroup(widget.group.groupId);
|
||||
groupSub = groupStream.listen((update) {
|
||||
if (update != null) {
|
||||
setState(() {
|
||||
group = update;
|
||||
});
|
||||
}
|
||||
});
|
||||
final membersStream =
|
||||
twonlyDB.groupsDao.watchGroupMembers(widget.group.groupId);
|
||||
membersSub = membersStream.listen((update) {
|
||||
setState(() {
|
||||
members = update;
|
||||
members.sort(
|
||||
(b, a) => a.$2.memberState!.index.compareTo(b.$2.memberState!.index),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _updateGroupName() async {
|
||||
final newGroupName = await showGroupNameChangeDialog(context, group);
|
||||
|
||||
if (context.mounted &&
|
||||
newGroupName != null &&
|
||||
newGroupName != '' &&
|
||||
newGroupName != group.groupName) {
|
||||
if (!await updateGroupeName(group, newGroupName)) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Network issue. Try again later.'),
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(''),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: AvatarIcon(
|
||||
group: group,
|
||||
fontSize: 30,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: VerifiedShield(key: GlobalKey(), group: group),
|
||||
),
|
||||
Text(
|
||||
substringBy(group.groupName, 25),
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 50),
|
||||
if (group.isGroupAdmin)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.pencil,
|
||||
text: context.lang.groupNameInput,
|
||||
onTap: _updateGroupName,
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(left: 17),
|
||||
child: Text(
|
||||
'${members.length + 1} ${context.lang.groupMembers}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (group.isGroupAdmin)
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.plus,
|
||||
text: context.lang.addMember,
|
||||
onTap: () => {},
|
||||
),
|
||||
BetterListTile(
|
||||
padding: const EdgeInsets.only(left: 13),
|
||||
leading: AvatarIcon(
|
||||
userData: gUser,
|
||||
fontSize: 16,
|
||||
),
|
||||
text: context.lang.you,
|
||||
trailing: (group.isGroupAdmin) ? Text(context.lang.admin) : null,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ProfileView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
...members.map((member) {
|
||||
return GroupMemberContextMenu(
|
||||
group: widget.group,
|
||||
contact: member.$1,
|
||||
member: member.$2,
|
||||
child: BetterListTile(
|
||||
padding: const EdgeInsets.only(left: 13),
|
||||
leading: AvatarIcon(
|
||||
contact: member.$1,
|
||||
fontSize: 16,
|
||||
),
|
||||
text: getContactDisplayName(member.$1, maxLength: 25),
|
||||
trailing: (member.$2.memberState == MemberState.admin)
|
||||
? Text(context.lang.admin)
|
||||
: null,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ContactView(member.$1.userId),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.rightFromBracket,
|
||||
color: Colors.red,
|
||||
text: context.lang.leaveGroup,
|
||||
onTap: () => {},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> showGroupNameChangeDialog(
|
||||
BuildContext context,
|
||||
Group group,
|
||||
) {
|
||||
final controller = TextEditingController(text: group.groupName);
|
||||
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(context.lang.groupNameInput),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(hintText: context.lang.groupNameInput),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text(context.lang.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(context.lang.ok),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(controller.text);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
83
lib/src/views/groups/group_member.context.dart
Normal file
83
lib/src/views/groups/group_member.context.dart
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages.view.dart';
|
||||
import 'package:twonly/src/views/components/context_menu.component.dart';
|
||||
|
||||
class GroupMemberContextMenu extends StatelessWidget {
|
||||
const GroupMemberContextMenu({
|
||||
required this.contact,
|
||||
required this.member,
|
||||
required this.child,
|
||||
required this.group,
|
||||
super.key,
|
||||
});
|
||||
final Contact contact;
|
||||
final GroupMember member;
|
||||
final Group group;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ContextMenu(
|
||||
items: [
|
||||
if (contact.accepted)
|
||||
ContextMenuItem(
|
||||
title: context.lang.contextMenuOpenChat,
|
||||
onTap: () async {
|
||||
final directChat =
|
||||
await twonlyDB.groupsDao.getDirectChat(contact.userId);
|
||||
if (directChat == null) {
|
||||
// create
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ChatMessagesView(directChat),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: FontAwesomeIcons.message,
|
||||
),
|
||||
if (!contact.accepted)
|
||||
ContextMenuItem(
|
||||
title: context.lang.createContactRequest,
|
||||
onTap: () async {
|
||||
// onResponseTriggered();
|
||||
},
|
||||
icon: FontAwesomeIcons.userPlus,
|
||||
),
|
||||
if (group.isGroupAdmin && member.memberState == MemberState.normal)
|
||||
ContextMenuItem(
|
||||
title: context.lang.makeAdmin,
|
||||
onTap: () async {
|
||||
// onResponseTriggered();
|
||||
},
|
||||
icon: FontAwesomeIcons.key,
|
||||
),
|
||||
if (group.isGroupAdmin && member.memberState == MemberState.admin)
|
||||
ContextMenuItem(
|
||||
title: context.lang.removeAdmin,
|
||||
onTap: () async {
|
||||
// onResponseTriggered();
|
||||
},
|
||||
icon: FontAwesomeIcons.key,
|
||||
),
|
||||
if (group.isGroupAdmin)
|
||||
ContextMenuItem(
|
||||
title: context.lang.removeFromGroup,
|
||||
onTap: () async {
|
||||
// onResponseTriggered();
|
||||
},
|
||||
icon: FontAwesomeIcons.rightFromBracket,
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue