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);
|
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(
|
Future<void> updateMember(
|
||||||
String groupId,
|
String groupId,
|
||||||
int contactId,
|
int contactId,
|
||||||
|
|
@ -139,6 +146,19 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
return query.map((row) => row.readTable(contacts)).watch();
|
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() {
|
Stream<List<Group>> watchGroups() {
|
||||||
return select(groups).watch();
|
return select(groups).watch();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,9 @@ class GroupHistories extends Table {
|
||||||
TextColumn get groupId =>
|
TextColumn get groupId =>
|
||||||
text().references(Groups, #groupId, onDelete: KeyAction.cascade)();
|
text().references(Groups, #groupId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
IntColumn get contactId =>
|
||||||
|
integer().nullable().references(Contacts, #userId)();
|
||||||
|
|
||||||
IntColumn get affectedContactId =>
|
IntColumn get affectedContactId =>
|
||||||
integer().nullable().references(Contacts, #userId)();
|
integer().nullable().references(Contacts, #userId)();
|
||||||
|
|
||||||
|
|
@ -95,3 +98,10 @@ class GroupHistories extends Table {
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {groupHistoryId};
|
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,
|
requiredDuringInsert: true,
|
||||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||||
'REFERENCES "groups" (group_id) ON DELETE CASCADE'));
|
'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 =
|
static const VerificationMeta _affectedContactIdMeta =
|
||||||
const VerificationMeta('affectedContactId');
|
const VerificationMeta('affectedContactId');
|
||||||
@override
|
@override
|
||||||
|
|
@ -6918,6 +6927,7 @@ class $GroupHistoriesTable extends GroupHistories
|
||||||
List<GeneratedColumn> get $columns => [
|
List<GeneratedColumn> get $columns => [
|
||||||
groupHistoryId,
|
groupHistoryId,
|
||||||
groupId,
|
groupId,
|
||||||
|
contactId,
|
||||||
affectedContactId,
|
affectedContactId,
|
||||||
oldGroupName,
|
oldGroupName,
|
||||||
newGroupName,
|
newGroupName,
|
||||||
|
|
@ -6948,6 +6958,10 @@ class $GroupHistoriesTable extends GroupHistories
|
||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_groupIdMeta);
|
context.missing(_groupIdMeta);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('contact_id')) {
|
||||||
|
context.handle(_contactIdMeta,
|
||||||
|
contactId.isAcceptableOrUnknown(data['contact_id']!, _contactIdMeta));
|
||||||
|
}
|
||||||
if (data.containsKey('affected_contact_id')) {
|
if (data.containsKey('affected_contact_id')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_affectedContactIdMeta,
|
_affectedContactIdMeta,
|
||||||
|
|
@ -6983,6 +6997,8 @@ class $GroupHistoriesTable extends GroupHistories
|
||||||
DriftSqlType.string, data['${effectivePrefix}group_history_id'])!,
|
DriftSqlType.string, data['${effectivePrefix}group_history_id'])!,
|
||||||
groupId: attachedDatabase.typeMapping
|
groupId: attachedDatabase.typeMapping
|
||||||
.read(DriftSqlType.string, data['${effectivePrefix}group_id'])!,
|
.read(DriftSqlType.string, data['${effectivePrefix}group_id'])!,
|
||||||
|
contactId: attachedDatabase.typeMapping
|
||||||
|
.read(DriftSqlType.int, data['${effectivePrefix}contact_id']),
|
||||||
affectedContactId: attachedDatabase.typeMapping.read(
|
affectedContactId: attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.int, data['${effectivePrefix}affected_contact_id']),
|
DriftSqlType.int, data['${effectivePrefix}affected_contact_id']),
|
||||||
oldGroupName: attachedDatabase.typeMapping
|
oldGroupName: attachedDatabase.typeMapping
|
||||||
|
|
@ -7009,6 +7025,7 @@ class $GroupHistoriesTable extends GroupHistories
|
||||||
class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
final String groupHistoryId;
|
final String groupHistoryId;
|
||||||
final String groupId;
|
final String groupId;
|
||||||
|
final int? contactId;
|
||||||
final int? affectedContactId;
|
final int? affectedContactId;
|
||||||
final String? oldGroupName;
|
final String? oldGroupName;
|
||||||
final String? newGroupName;
|
final String? newGroupName;
|
||||||
|
|
@ -7017,6 +7034,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
const GroupHistory(
|
const GroupHistory(
|
||||||
{required this.groupHistoryId,
|
{required this.groupHistoryId,
|
||||||
required this.groupId,
|
required this.groupId,
|
||||||
|
this.contactId,
|
||||||
this.affectedContactId,
|
this.affectedContactId,
|
||||||
this.oldGroupName,
|
this.oldGroupName,
|
||||||
this.newGroupName,
|
this.newGroupName,
|
||||||
|
|
@ -7027,6 +7045,9 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
final map = <String, Expression>{};
|
final map = <String, Expression>{};
|
||||||
map['group_history_id'] = Variable<String>(groupHistoryId);
|
map['group_history_id'] = Variable<String>(groupHistoryId);
|
||||||
map['group_id'] = Variable<String>(groupId);
|
map['group_id'] = Variable<String>(groupId);
|
||||||
|
if (!nullToAbsent || contactId != null) {
|
||||||
|
map['contact_id'] = Variable<int>(contactId);
|
||||||
|
}
|
||||||
if (!nullToAbsent || affectedContactId != null) {
|
if (!nullToAbsent || affectedContactId != null) {
|
||||||
map['affected_contact_id'] = Variable<int>(affectedContactId);
|
map['affected_contact_id'] = Variable<int>(affectedContactId);
|
||||||
}
|
}
|
||||||
|
|
@ -7048,6 +7069,9 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
return GroupHistoriesCompanion(
|
return GroupHistoriesCompanion(
|
||||||
groupHistoryId: Value(groupHistoryId),
|
groupHistoryId: Value(groupHistoryId),
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
|
contactId: contactId == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(contactId),
|
||||||
affectedContactId: affectedContactId == null && nullToAbsent
|
affectedContactId: affectedContactId == null && nullToAbsent
|
||||||
? const Value.absent()
|
? const Value.absent()
|
||||||
: Value(affectedContactId),
|
: Value(affectedContactId),
|
||||||
|
|
@ -7068,6 +7092,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
return GroupHistory(
|
return GroupHistory(
|
||||||
groupHistoryId: serializer.fromJson<String>(json['groupHistoryId']),
|
groupHistoryId: serializer.fromJson<String>(json['groupHistoryId']),
|
||||||
groupId: serializer.fromJson<String>(json['groupId']),
|
groupId: serializer.fromJson<String>(json['groupId']),
|
||||||
|
contactId: serializer.fromJson<int?>(json['contactId']),
|
||||||
affectedContactId: serializer.fromJson<int?>(json['affectedContactId']),
|
affectedContactId: serializer.fromJson<int?>(json['affectedContactId']),
|
||||||
oldGroupName: serializer.fromJson<String?>(json['oldGroupName']),
|
oldGroupName: serializer.fromJson<String?>(json['oldGroupName']),
|
||||||
newGroupName: serializer.fromJson<String?>(json['newGroupName']),
|
newGroupName: serializer.fromJson<String?>(json['newGroupName']),
|
||||||
|
|
@ -7082,6 +7107,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'groupHistoryId': serializer.toJson<String>(groupHistoryId),
|
'groupHistoryId': serializer.toJson<String>(groupHistoryId),
|
||||||
'groupId': serializer.toJson<String>(groupId),
|
'groupId': serializer.toJson<String>(groupId),
|
||||||
|
'contactId': serializer.toJson<int?>(contactId),
|
||||||
'affectedContactId': serializer.toJson<int?>(affectedContactId),
|
'affectedContactId': serializer.toJson<int?>(affectedContactId),
|
||||||
'oldGroupName': serializer.toJson<String?>(oldGroupName),
|
'oldGroupName': serializer.toJson<String?>(oldGroupName),
|
||||||
'newGroupName': serializer.toJson<String?>(newGroupName),
|
'newGroupName': serializer.toJson<String?>(newGroupName),
|
||||||
|
|
@ -7094,6 +7120,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
GroupHistory copyWith(
|
GroupHistory copyWith(
|
||||||
{String? groupHistoryId,
|
{String? groupHistoryId,
|
||||||
String? groupId,
|
String? groupId,
|
||||||
|
Value<int?> contactId = const Value.absent(),
|
||||||
Value<int?> affectedContactId = const Value.absent(),
|
Value<int?> affectedContactId = const Value.absent(),
|
||||||
Value<String?> oldGroupName = const Value.absent(),
|
Value<String?> oldGroupName = const Value.absent(),
|
||||||
Value<String?> newGroupName = const Value.absent(),
|
Value<String?> newGroupName = const Value.absent(),
|
||||||
|
|
@ -7102,6 +7129,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
GroupHistory(
|
GroupHistory(
|
||||||
groupHistoryId: groupHistoryId ?? this.groupHistoryId,
|
groupHistoryId: groupHistoryId ?? this.groupHistoryId,
|
||||||
groupId: groupId ?? this.groupId,
|
groupId: groupId ?? this.groupId,
|
||||||
|
contactId: contactId.present ? contactId.value : this.contactId,
|
||||||
affectedContactId: affectedContactId.present
|
affectedContactId: affectedContactId.present
|
||||||
? affectedContactId.value
|
? affectedContactId.value
|
||||||
: this.affectedContactId,
|
: this.affectedContactId,
|
||||||
|
|
@ -7118,6 +7146,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
? data.groupHistoryId.value
|
? data.groupHistoryId.value
|
||||||
: this.groupHistoryId,
|
: this.groupHistoryId,
|
||||||
groupId: data.groupId.present ? data.groupId.value : this.groupId,
|
groupId: data.groupId.present ? data.groupId.value : this.groupId,
|
||||||
|
contactId: data.contactId.present ? data.contactId.value : this.contactId,
|
||||||
affectedContactId: data.affectedContactId.present
|
affectedContactId: data.affectedContactId.present
|
||||||
? data.affectedContactId.value
|
? data.affectedContactId.value
|
||||||
: this.affectedContactId,
|
: this.affectedContactId,
|
||||||
|
|
@ -7137,6 +7166,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
return (StringBuffer('GroupHistory(')
|
return (StringBuffer('GroupHistory(')
|
||||||
..write('groupHistoryId: $groupHistoryId, ')
|
..write('groupHistoryId: $groupHistoryId, ')
|
||||||
..write('groupId: $groupId, ')
|
..write('groupId: $groupId, ')
|
||||||
|
..write('contactId: $contactId, ')
|
||||||
..write('affectedContactId: $affectedContactId, ')
|
..write('affectedContactId: $affectedContactId, ')
|
||||||
..write('oldGroupName: $oldGroupName, ')
|
..write('oldGroupName: $oldGroupName, ')
|
||||||
..write('newGroupName: $newGroupName, ')
|
..write('newGroupName: $newGroupName, ')
|
||||||
|
|
@ -7147,14 +7177,15 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(groupHistoryId, groupId, affectedContactId,
|
int get hashCode => Object.hash(groupHistoryId, groupId, contactId,
|
||||||
oldGroupName, newGroupName, type, actionAt);
|
affectedContactId, oldGroupName, newGroupName, type, actionAt);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is GroupHistory &&
|
(other is GroupHistory &&
|
||||||
other.groupHistoryId == this.groupHistoryId &&
|
other.groupHistoryId == this.groupHistoryId &&
|
||||||
other.groupId == this.groupId &&
|
other.groupId == this.groupId &&
|
||||||
|
other.contactId == this.contactId &&
|
||||||
other.affectedContactId == this.affectedContactId &&
|
other.affectedContactId == this.affectedContactId &&
|
||||||
other.oldGroupName == this.oldGroupName &&
|
other.oldGroupName == this.oldGroupName &&
|
||||||
other.newGroupName == this.newGroupName &&
|
other.newGroupName == this.newGroupName &&
|
||||||
|
|
@ -7165,6 +7196,7 @@ class GroupHistory extends DataClass implements Insertable<GroupHistory> {
|
||||||
class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
final Value<String> groupHistoryId;
|
final Value<String> groupHistoryId;
|
||||||
final Value<String> groupId;
|
final Value<String> groupId;
|
||||||
|
final Value<int?> contactId;
|
||||||
final Value<int?> affectedContactId;
|
final Value<int?> affectedContactId;
|
||||||
final Value<String?> oldGroupName;
|
final Value<String?> oldGroupName;
|
||||||
final Value<String?> newGroupName;
|
final Value<String?> newGroupName;
|
||||||
|
|
@ -7174,6 +7206,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
const GroupHistoriesCompanion({
|
const GroupHistoriesCompanion({
|
||||||
this.groupHistoryId = const Value.absent(),
|
this.groupHistoryId = const Value.absent(),
|
||||||
this.groupId = const Value.absent(),
|
this.groupId = const Value.absent(),
|
||||||
|
this.contactId = const Value.absent(),
|
||||||
this.affectedContactId = const Value.absent(),
|
this.affectedContactId = const Value.absent(),
|
||||||
this.oldGroupName = const Value.absent(),
|
this.oldGroupName = const Value.absent(),
|
||||||
this.newGroupName = const Value.absent(),
|
this.newGroupName = const Value.absent(),
|
||||||
|
|
@ -7184,6 +7217,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
GroupHistoriesCompanion.insert({
|
GroupHistoriesCompanion.insert({
|
||||||
required String groupHistoryId,
|
required String groupHistoryId,
|
||||||
required String groupId,
|
required String groupId,
|
||||||
|
this.contactId = const Value.absent(),
|
||||||
this.affectedContactId = const Value.absent(),
|
this.affectedContactId = const Value.absent(),
|
||||||
this.oldGroupName = const Value.absent(),
|
this.oldGroupName = const Value.absent(),
|
||||||
this.newGroupName = const Value.absent(),
|
this.newGroupName = const Value.absent(),
|
||||||
|
|
@ -7196,6 +7230,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
static Insertable<GroupHistory> custom({
|
static Insertable<GroupHistory> custom({
|
||||||
Expression<String>? groupHistoryId,
|
Expression<String>? groupHistoryId,
|
||||||
Expression<String>? groupId,
|
Expression<String>? groupId,
|
||||||
|
Expression<int>? contactId,
|
||||||
Expression<int>? affectedContactId,
|
Expression<int>? affectedContactId,
|
||||||
Expression<String>? oldGroupName,
|
Expression<String>? oldGroupName,
|
||||||
Expression<String>? newGroupName,
|
Expression<String>? newGroupName,
|
||||||
|
|
@ -7206,6 +7241,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
if (groupHistoryId != null) 'group_history_id': groupHistoryId,
|
if (groupHistoryId != null) 'group_history_id': groupHistoryId,
|
||||||
if (groupId != null) 'group_id': groupId,
|
if (groupId != null) 'group_id': groupId,
|
||||||
|
if (contactId != null) 'contact_id': contactId,
|
||||||
if (affectedContactId != null) 'affected_contact_id': affectedContactId,
|
if (affectedContactId != null) 'affected_contact_id': affectedContactId,
|
||||||
if (oldGroupName != null) 'old_group_name': oldGroupName,
|
if (oldGroupName != null) 'old_group_name': oldGroupName,
|
||||||
if (newGroupName != null) 'new_group_name': newGroupName,
|
if (newGroupName != null) 'new_group_name': newGroupName,
|
||||||
|
|
@ -7218,6 +7254,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
GroupHistoriesCompanion copyWith(
|
GroupHistoriesCompanion copyWith(
|
||||||
{Value<String>? groupHistoryId,
|
{Value<String>? groupHistoryId,
|
||||||
Value<String>? groupId,
|
Value<String>? groupId,
|
||||||
|
Value<int?>? contactId,
|
||||||
Value<int?>? affectedContactId,
|
Value<int?>? affectedContactId,
|
||||||
Value<String?>? oldGroupName,
|
Value<String?>? oldGroupName,
|
||||||
Value<String?>? newGroupName,
|
Value<String?>? newGroupName,
|
||||||
|
|
@ -7227,6 +7264,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
return GroupHistoriesCompanion(
|
return GroupHistoriesCompanion(
|
||||||
groupHistoryId: groupHistoryId ?? this.groupHistoryId,
|
groupHistoryId: groupHistoryId ?? this.groupHistoryId,
|
||||||
groupId: groupId ?? this.groupId,
|
groupId: groupId ?? this.groupId,
|
||||||
|
contactId: contactId ?? this.contactId,
|
||||||
affectedContactId: affectedContactId ?? this.affectedContactId,
|
affectedContactId: affectedContactId ?? this.affectedContactId,
|
||||||
oldGroupName: oldGroupName ?? this.oldGroupName,
|
oldGroupName: oldGroupName ?? this.oldGroupName,
|
||||||
newGroupName: newGroupName ?? this.newGroupName,
|
newGroupName: newGroupName ?? this.newGroupName,
|
||||||
|
|
@ -7245,6 +7283,9 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
if (groupId.present) {
|
if (groupId.present) {
|
||||||
map['group_id'] = Variable<String>(groupId.value);
|
map['group_id'] = Variable<String>(groupId.value);
|
||||||
}
|
}
|
||||||
|
if (contactId.present) {
|
||||||
|
map['contact_id'] = Variable<int>(contactId.value);
|
||||||
|
}
|
||||||
if (affectedContactId.present) {
|
if (affectedContactId.present) {
|
||||||
map['affected_contact_id'] = Variable<int>(affectedContactId.value);
|
map['affected_contact_id'] = Variable<int>(affectedContactId.value);
|
||||||
}
|
}
|
||||||
|
|
@ -7272,6 +7313,7 @@ class GroupHistoriesCompanion extends UpdateCompanion<GroupHistory> {
|
||||||
return (StringBuffer('GroupHistoriesCompanion(')
|
return (StringBuffer('GroupHistoriesCompanion(')
|
||||||
..write('groupHistoryId: $groupHistoryId, ')
|
..write('groupHistoryId: $groupHistoryId, ')
|
||||||
..write('groupId: $groupId, ')
|
..write('groupId: $groupId, ')
|
||||||
|
..write('contactId: $contactId, ')
|
||||||
..write('affectedContactId: $affectedContactId, ')
|
..write('affectedContactId: $affectedContactId, ')
|
||||||
..write('oldGroupName: $oldGroupName, ')
|
..write('oldGroupName: $oldGroupName, ')
|
||||||
..write('newGroupName: $newGroupName, ')
|
..write('newGroupName: $newGroupName, ')
|
||||||
|
|
@ -7568,22 +7610,6 @@ final class $$ContactsTableReferences
|
||||||
return ProcessedTableManager(
|
return ProcessedTableManager(
|
||||||
manager.$state.copyWith(prefetchedData: cache));
|
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
|
class $$ContactsTableFilterComposer
|
||||||
|
|
@ -7766,27 +7792,6 @@ class $$ContactsTableFilterComposer
|
||||||
));
|
));
|
||||||
return f(composer);
|
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
|
class $$ContactsTableOrderingComposer
|
||||||
|
|
@ -8020,27 +8025,6 @@ class $$ContactsTableAnnotationComposer
|
||||||
));
|
));
|
||||||
return f(composer);
|
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<
|
class $$ContactsTableTableManager extends RootTableManager<
|
||||||
|
|
@ -8060,8 +8044,7 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
bool groupMembersRefs,
|
bool groupMembersRefs,
|
||||||
bool receiptsRefs,
|
bool receiptsRefs,
|
||||||
bool signalContactPreKeysRefs,
|
bool signalContactPreKeysRefs,
|
||||||
bool signalContactSignedPreKeysRefs,
|
bool signalContactSignedPreKeysRefs})> {
|
||||||
bool groupHistoriesRefs})> {
|
|
||||||
$$ContactsTableTableManager(_$TwonlyDB db, $ContactsTable table)
|
$$ContactsTableTableManager(_$TwonlyDB db, $ContactsTable table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
db: db,
|
db: db,
|
||||||
|
|
@ -8142,8 +8125,7 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
groupMembersRefs = false,
|
groupMembersRefs = false,
|
||||||
receiptsRefs = false,
|
receiptsRefs = false,
|
||||||
signalContactPreKeysRefs = false,
|
signalContactPreKeysRefs = false,
|
||||||
signalContactSignedPreKeysRefs = false,
|
signalContactSignedPreKeysRefs = false}) {
|
||||||
groupHistoriesRefs = false}) {
|
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [
|
explicitlyWatchedTables: [
|
||||||
|
|
@ -8153,8 +8135,7 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
if (receiptsRefs) db.receipts,
|
if (receiptsRefs) db.receipts,
|
||||||
if (signalContactPreKeysRefs) db.signalContactPreKeys,
|
if (signalContactPreKeysRefs) db.signalContactPreKeys,
|
||||||
if (signalContactSignedPreKeysRefs)
|
if (signalContactSignedPreKeysRefs)
|
||||||
db.signalContactSignedPreKeys,
|
db.signalContactSignedPreKeys
|
||||||
if (groupHistoriesRefs) db.groupHistories
|
|
||||||
],
|
],
|
||||||
addJoins: null,
|
addJoins: null,
|
||||||
getPrefetchedDataCallback: (items) async {
|
getPrefetchedDataCallback: (items) async {
|
||||||
|
|
@ -8234,19 +8215,6 @@ class $$ContactsTableTableManager extends RootTableManager<
|
||||||
referencedItemsForCurrentItem:
|
referencedItemsForCurrentItem:
|
||||||
(item, referencedItems) => referencedItems
|
(item, referencedItems) => referencedItems
|
||||||
.where((e) => e.contactId == item.userId),
|
.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)
|
typedResults: items)
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
@ -8272,8 +8240,7 @@ typedef $$ContactsTableProcessedTableManager = ProcessedTableManager<
|
||||||
bool groupMembersRefs,
|
bool groupMembersRefs,
|
||||||
bool receiptsRefs,
|
bool receiptsRefs,
|
||||||
bool signalContactPreKeysRefs,
|
bool signalContactPreKeysRefs,
|
||||||
bool signalContactSignedPreKeysRefs,
|
bool signalContactSignedPreKeysRefs})>;
|
||||||
bool groupHistoriesRefs})>;
|
|
||||||
typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
typedef $$GroupsTableCreateCompanionBuilder = GroupsCompanion Function({
|
||||||
required String groupId,
|
required String groupId,
|
||||||
Value<bool> isGroupAdmin,
|
Value<bool> isGroupAdmin,
|
||||||
|
|
@ -13213,6 +13180,7 @@ typedef $$GroupHistoriesTableCreateCompanionBuilder = GroupHistoriesCompanion
|
||||||
Function({
|
Function({
|
||||||
required String groupHistoryId,
|
required String groupHistoryId,
|
||||||
required String groupId,
|
required String groupId,
|
||||||
|
Value<int?> contactId,
|
||||||
Value<int?> affectedContactId,
|
Value<int?> affectedContactId,
|
||||||
Value<String?> oldGroupName,
|
Value<String?> oldGroupName,
|
||||||
Value<String?> newGroupName,
|
Value<String?> newGroupName,
|
||||||
|
|
@ -13224,6 +13192,7 @@ typedef $$GroupHistoriesTableUpdateCompanionBuilder = GroupHistoriesCompanion
|
||||||
Function({
|
Function({
|
||||||
Value<String> groupHistoryId,
|
Value<String> groupHistoryId,
|
||||||
Value<String> groupId,
|
Value<String> groupId,
|
||||||
|
Value<int?> contactId,
|
||||||
Value<int?> affectedContactId,
|
Value<int?> affectedContactId,
|
||||||
Value<String?> oldGroupName,
|
Value<String?> oldGroupName,
|
||||||
Value<String?> newGroupName,
|
Value<String?> newGroupName,
|
||||||
|
|
@ -13251,6 +13220,21 @@ final class $$GroupHistoriesTableReferences
|
||||||
manager.$state.copyWith(prefetchedData: [item]));
|
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) =>
|
static $ContactsTable _affectedContactIdTable(_$TwonlyDB db) =>
|
||||||
db.contacts.createAlias($_aliasNameGenerator(
|
db.contacts.createAlias($_aliasNameGenerator(
|
||||||
db.groupHistories.affectedContactId, db.contacts.userId));
|
db.groupHistories.affectedContactId, db.contacts.userId));
|
||||||
|
|
@ -13314,6 +13298,26 @@ class $$GroupHistoriesTableFilterComposer
|
||||||
return composer;
|
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 {
|
$$ContactsTableFilterComposer get affectedContactId {
|
||||||
final $$ContactsTableFilterComposer composer = $composerBuilder(
|
final $$ContactsTableFilterComposer composer = $composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
|
|
@ -13382,6 +13386,26 @@ class $$GroupHistoriesTableOrderingComposer
|
||||||
return composer;
|
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 {
|
$$ContactsTableOrderingComposer get affectedContactId {
|
||||||
final $$ContactsTableOrderingComposer composer = $composerBuilder(
|
final $$ContactsTableOrderingComposer composer = $composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
|
|
@ -13447,6 +13471,26 @@ class $$GroupHistoriesTableAnnotationComposer
|
||||||
return composer;
|
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 {
|
$$ContactsTableAnnotationComposer get affectedContactId {
|
||||||
final $$ContactsTableAnnotationComposer composer = $composerBuilder(
|
final $$ContactsTableAnnotationComposer composer = $composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
|
|
@ -13479,7 +13523,8 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
$$GroupHistoriesTableUpdateCompanionBuilder,
|
$$GroupHistoriesTableUpdateCompanionBuilder,
|
||||||
(GroupHistory, $$GroupHistoriesTableReferences),
|
(GroupHistory, $$GroupHistoriesTableReferences),
|
||||||
GroupHistory,
|
GroupHistory,
|
||||||
PrefetchHooks Function({bool groupId, bool affectedContactId})> {
|
PrefetchHooks Function(
|
||||||
|
{bool groupId, bool contactId, bool affectedContactId})> {
|
||||||
$$GroupHistoriesTableTableManager(_$TwonlyDB db, $GroupHistoriesTable table)
|
$$GroupHistoriesTableTableManager(_$TwonlyDB db, $GroupHistoriesTable table)
|
||||||
: super(TableManagerState(
|
: super(TableManagerState(
|
||||||
db: db,
|
db: db,
|
||||||
|
|
@ -13493,6 +13538,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
updateCompanionCallback: ({
|
updateCompanionCallback: ({
|
||||||
Value<String> groupHistoryId = const Value.absent(),
|
Value<String> groupHistoryId = const Value.absent(),
|
||||||
Value<String> groupId = const Value.absent(),
|
Value<String> groupId = const Value.absent(),
|
||||||
|
Value<int?> contactId = const Value.absent(),
|
||||||
Value<int?> affectedContactId = const Value.absent(),
|
Value<int?> affectedContactId = const Value.absent(),
|
||||||
Value<String?> oldGroupName = const Value.absent(),
|
Value<String?> oldGroupName = const Value.absent(),
|
||||||
Value<String?> newGroupName = const Value.absent(),
|
Value<String?> newGroupName = const Value.absent(),
|
||||||
|
|
@ -13503,6 +13549,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
GroupHistoriesCompanion(
|
GroupHistoriesCompanion(
|
||||||
groupHistoryId: groupHistoryId,
|
groupHistoryId: groupHistoryId,
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
|
contactId: contactId,
|
||||||
affectedContactId: affectedContactId,
|
affectedContactId: affectedContactId,
|
||||||
oldGroupName: oldGroupName,
|
oldGroupName: oldGroupName,
|
||||||
newGroupName: newGroupName,
|
newGroupName: newGroupName,
|
||||||
|
|
@ -13513,6 +13560,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
createCompanionCallback: ({
|
createCompanionCallback: ({
|
||||||
required String groupHistoryId,
|
required String groupHistoryId,
|
||||||
required String groupId,
|
required String groupId,
|
||||||
|
Value<int?> contactId = const Value.absent(),
|
||||||
Value<int?> affectedContactId = const Value.absent(),
|
Value<int?> affectedContactId = const Value.absent(),
|
||||||
Value<String?> oldGroupName = const Value.absent(),
|
Value<String?> oldGroupName = const Value.absent(),
|
||||||
Value<String?> newGroupName = const Value.absent(),
|
Value<String?> newGroupName = const Value.absent(),
|
||||||
|
|
@ -13523,6 +13571,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
GroupHistoriesCompanion.insert(
|
GroupHistoriesCompanion.insert(
|
||||||
groupHistoryId: groupHistoryId,
|
groupHistoryId: groupHistoryId,
|
||||||
groupId: groupId,
|
groupId: groupId,
|
||||||
|
contactId: contactId,
|
||||||
affectedContactId: affectedContactId,
|
affectedContactId: affectedContactId,
|
||||||
oldGroupName: oldGroupName,
|
oldGroupName: oldGroupName,
|
||||||
newGroupName: newGroupName,
|
newGroupName: newGroupName,
|
||||||
|
|
@ -13537,7 +13586,7 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
))
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
prefetchHooksCallback: (
|
prefetchHooksCallback: (
|
||||||
{groupId = false, affectedContactId = false}) {
|
{groupId = false, contactId = false, affectedContactId = false}) {
|
||||||
return PrefetchHooks(
|
return PrefetchHooks(
|
||||||
db: db,
|
db: db,
|
||||||
explicitlyWatchedTables: [],
|
explicitlyWatchedTables: [],
|
||||||
|
|
@ -13565,6 +13614,17 @@ class $$GroupHistoriesTableTableManager extends RootTableManager<
|
||||||
.groupId,
|
.groupId,
|
||||||
) as T;
|
) 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) {
|
if (affectedContactId) {
|
||||||
state = state.withJoin(
|
state = state.withJoin(
|
||||||
currentTable: table,
|
currentTable: table,
|
||||||
|
|
@ -13598,7 +13658,8 @@ typedef $$GroupHistoriesTableProcessedTableManager = ProcessedTableManager<
|
||||||
$$GroupHistoriesTableUpdateCompanionBuilder,
|
$$GroupHistoriesTableUpdateCompanionBuilder,
|
||||||
(GroupHistory, $$GroupHistoriesTableReferences),
|
(GroupHistory, $$GroupHistoriesTableReferences),
|
||||||
GroupHistory,
|
GroupHistory,
|
||||||
PrefetchHooks Function({bool groupId, bool affectedContactId})>;
|
PrefetchHooks Function(
|
||||||
|
{bool groupId, bool contactId, bool affectedContactId})>;
|
||||||
|
|
||||||
class $TwonlyDBManager {
|
class $TwonlyDBManager {
|
||||||
final _$TwonlyDB _db;
|
final _$TwonlyDB _db;
|
||||||
|
|
|
||||||
|
|
@ -365,5 +365,12 @@
|
||||||
"selectGroupName": "Gruppennamen wählen",
|
"selectGroupName": "Gruppennamen wählen",
|
||||||
"groupNameInput": "Gruppennamen",
|
"groupNameInput": "Gruppennamen",
|
||||||
"groupMembers": "Mitglieder",
|
"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",
|
"selectGroupName": "Select group name",
|
||||||
"groupNameInput": "Group name",
|
"groupNameInput": "Group name",
|
||||||
"groupMembers": "Members",
|
"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'**
|
/// **'Members'**
|
||||||
String get groupMembers;
|
String get groupMembers;
|
||||||
|
|
||||||
|
/// No description provided for @addMember.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Add member'**
|
||||||
|
String get addMember;
|
||||||
|
|
||||||
/// No description provided for @createGroup.
|
/// No description provided for @createGroup.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Create group'**
|
/// **'Create group'**
|
||||||
String get createGroup;
|
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
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1181,6 +1181,27 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get groupMembers => 'Mitglieder';
|
String get groupMembers => 'Mitglieder';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addMember => 'Mitglied hinzufügen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get createGroup => 'Gruppe erstellen';
|
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
|
@override
|
||||||
String get groupMembers => 'Members';
|
String get groupMembers => 'Members';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get addMember => 'Add member';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get createGroup => 'Create group';
|
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 'dart:async';
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -29,7 +28,7 @@ Future<void> handleGroupCreate(
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
stateVersionId: const Value(0),
|
stateVersionId: const Value(0),
|
||||||
stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)),
|
stateEncryptionKey: Value(Uint8List.fromList(newGroup.stateKey)),
|
||||||
myGroupPrivateKey: Value(myGroupKey.getPrivateKey().serialize()),
|
myGroupPrivateKey: Value(myGroupKey.serialize()),
|
||||||
groupName: const Value(''),
|
groupName: const Value(''),
|
||||||
joinedGroup: const Value(false),
|
joinedGroup: const Value(false),
|
||||||
),
|
),
|
||||||
|
|
@ -81,7 +80,53 @@ Future<void> handleGroupUpdate(
|
||||||
int fromUserId,
|
int fromUserId,
|
||||||
String groupId,
|
String groupId,
|
||||||
EncryptedContent_GroupUpdate update,
|
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(
|
Future<bool> handleGroupJoin(
|
||||||
int fromUserId,
|
int fromUserId,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
import 'package:cryptography_flutter_plus/cryptography_flutter_plus.dart';
|
||||||
import 'package:cryptography_plus/cryptography_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:hashlib/random.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
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/globals.dart';
|
||||||
import 'package:twonly/src/database/tables/groups.table.dart';
|
import 'package:twonly/src/database/tables/groups.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
|
@ -23,6 +26,10 @@ String getGroupStateUrl() {
|
||||||
return 'http${apiService.apiSecure}://${apiService.apiHost}/api/group/state';
|
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 {
|
Future<bool> createNewGroup(String groupName, List<Contact> members) async {
|
||||||
// First: Upload new State to the server.....
|
// First: Upload new State to the server.....
|
||||||
// if (groupName) return;
|
// if (groupName) return;
|
||||||
|
|
@ -90,7 +97,7 @@ Future<bool> createNewGroup(String groupName, List<Contact> members) async {
|
||||||
isGroupAdmin: const Value(true),
|
isGroupAdmin: const Value(true),
|
||||||
stateEncryptionKey: Value(stateEncryptionKey),
|
stateEncryptionKey: Value(stateEncryptionKey),
|
||||||
stateVersionId: const Value(1),
|
stateVersionId: const Value(1),
|
||||||
myGroupPrivateKey: Value(myGroupKey.getPrivateKey().serialize()),
|
myGroupPrivateKey: Value(myGroupKey.serialize()),
|
||||||
joinedGroup: const Value(true),
|
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 {
|
try {
|
||||||
var isSuccess = true;
|
var isSuccess = true;
|
||||||
|
|
||||||
|
|
@ -156,7 +163,7 @@ Future<bool> fetchGroupState(Group group) async {
|
||||||
Log.error(
|
Log.error(
|
||||||
'Could not load group state. Got status code ${response.statusCode} from server.',
|
'Could not load group state. Got status code ${response.statusCode} from server.',
|
||||||
);
|
);
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final groupStateServer = GroupState.fromBuffer(response.bodyBytes);
|
final groupStateServer = GroupState.fromBuffer(response.bodyBytes);
|
||||||
|
|
@ -180,8 +187,10 @@ Future<bool> fetchGroupState(Group group) async {
|
||||||
EncryptedGroupState.fromBuffer(encryptedGroupStateRaw);
|
EncryptedGroupState.fromBuffer(encryptedGroupStateRaw);
|
||||||
|
|
||||||
if (group.stateVersionId >= groupStateServer.versionId.toInt()) {
|
if (group.stateVersionId >= groupStateServer.versionId.toInt()) {
|
||||||
Log.error('Group ${group.groupId} has newest group state');
|
Log.info(
|
||||||
return false;
|
'Group ${group.groupId} has already newest group state from the server!',
|
||||||
|
);
|
||||||
|
return (groupStateServer.versionId.toInt(), encryptedGroupState);
|
||||||
}
|
}
|
||||||
|
|
||||||
final isGroupAdmin = encryptedGroupState.adminIds
|
final isGroupAdmin = encryptedGroupState.adminIds
|
||||||
|
|
@ -204,6 +213,9 @@ Future<bool> fetchGroupState(Group group) async {
|
||||||
|
|
||||||
// First find and insert NEW members
|
// First find and insert NEW members
|
||||||
for (final memberId in encryptedGroupState.memberIds) {
|
for (final memberId in encryptedGroupState.memberIds) {
|
||||||
|
if (memberId == Int64(gUser.userId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (currentGroupMembers.any((t) => t.contactId == memberId.toInt())) {
|
if (currentGroupMembers.any((t) => t.contactId == memberId.toInt())) {
|
||||||
// User is already in the database
|
// User is already in the database
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -280,10 +292,10 @@ Future<bool> fetchGroupState(Group group) async {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return (groupStateServer.versionId.toInt(), encryptedGroupState);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,3 +316,112 @@ Future<bool> addNewHiddenContact(int contactId) async {
|
||||||
await createNewSignalSession(userData);
|
await createNewSignalSession(userData);
|
||||||
return true;
|
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/avatar_icon.component.dart';
|
||||||
import 'package:twonly/src/views/components/flame.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/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 {
|
class GroupListItem extends StatefulWidget {
|
||||||
const GroupListItem({
|
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(
|
trailing: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.push(
|
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/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_send_to_view.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_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/chat_list_entry.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/response_container.dart';
|
||||||
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
import 'package:twonly/src/views/components/avatar_icon.component.dart';
|
||||||
|
|
@ -30,7 +31,12 @@ Color getMessageColor(Message message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatItem {
|
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) {
|
factory ChatItem.date(DateTime date) {
|
||||||
return ChatItem._(date: date);
|
return ChatItem._(date: date);
|
||||||
}
|
}
|
||||||
|
|
@ -40,11 +46,16 @@ class ChatItem {
|
||||||
factory ChatItem.lastOpenedPosition(List<Contact> contacts) {
|
factory ChatItem.lastOpenedPosition(List<Contact> contacts) {
|
||||||
return ChatItem._(lastOpenedPosition: contacts);
|
return ChatItem._(lastOpenedPosition: contacts);
|
||||||
}
|
}
|
||||||
|
factory ChatItem.groupAction(GroupHistory groupAction) {
|
||||||
|
return ChatItem._(groupAction: groupAction);
|
||||||
|
}
|
||||||
|
final GroupHistory? groupAction;
|
||||||
final Message? message;
|
final Message? message;
|
||||||
final DateTime? date;
|
final DateTime? date;
|
||||||
final List<Contact>? lastOpenedPosition;
|
final List<Contact>? lastOpenedPosition;
|
||||||
bool get isMessage => message != null;
|
bool get isMessage => message != null;
|
||||||
bool get isDate => date != null;
|
bool get isDate => date != null;
|
||||||
|
bool get isGroupAction => groupAction != null;
|
||||||
bool get isLastOpenedPosition => lastOpenedPosition != null;
|
bool get isLastOpenedPosition => lastOpenedPosition != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,12 +76,14 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
String currentInputText = '';
|
String currentInputText = '';
|
||||||
late StreamSubscription<Group?> userSub;
|
late StreamSubscription<Group?> userSub;
|
||||||
late StreamSubscription<List<Message>> messageSub;
|
late StreamSubscription<List<Message>> messageSub;
|
||||||
|
late StreamSubscription<List<GroupHistory>>? groupActionsSub;
|
||||||
late StreamSubscription<Future<List<(Message, Contact)>>>?
|
late StreamSubscription<Future<List<(Message, Contact)>>>?
|
||||||
lastOpenedMessageByContactSub;
|
lastOpenedMessageByContactSub;
|
||||||
|
|
||||||
List<ChatItem> messages = [];
|
List<ChatItem> messages = [];
|
||||||
List<Message> allMessages = [];
|
List<Message> allMessages = [];
|
||||||
List<(Message, Contact)> lastOpenedMessageByContact = [];
|
List<(Message, Contact)> lastOpenedMessageByContact = [];
|
||||||
|
List<GroupHistory> groupActions = [];
|
||||||
List<MemoryItem> galleryItems = [];
|
List<MemoryItem> galleryItems = [];
|
||||||
Message? quotesMessage;
|
Message? quotesMessage;
|
||||||
GlobalKey verifyShieldKey = GlobalKey();
|
GlobalKey verifyShieldKey = GlobalKey();
|
||||||
|
|
@ -97,6 +110,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
void dispose() {
|
void dispose() {
|
||||||
userSub.cancel();
|
userSub.cancel();
|
||||||
messageSub.cancel();
|
messageSub.cancel();
|
||||||
|
groupActionsSub?.cancel();
|
||||||
lastOpenedMessageByContactSub?.cancel();
|
lastOpenedMessageByContactSub?.cancel();
|
||||||
tutorial?.cancel();
|
tutorial?.cancel();
|
||||||
textFieldFocus.dispose();
|
textFieldFocus.dispose();
|
||||||
|
|
@ -121,7 +135,13 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
lastOpenedStream.listen((lastActionsFuture) async {
|
lastOpenedStream.listen((lastActionsFuture) async {
|
||||||
final update = await lastActionsFuture;
|
final update = await lastActionsFuture;
|
||||||
lastOpenedMessageByContact = update;
|
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;
|
// return;
|
||||||
}
|
}
|
||||||
await protectMessageUpdating.protect(() async {
|
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(
|
Future<void> setMessages(
|
||||||
List<Message> newMessages,
|
List<Message> newMessages,
|
||||||
List<(Message, Contact)> lastOpenedMessageByContact,
|
List<(Message, Contact)> lastOpenedMessageByContact,
|
||||||
|
List<GroupHistory> groupActions,
|
||||||
) async {
|
) async {
|
||||||
await flutterLocalNotificationsPlugin.cancelAll();
|
await flutterLocalNotificationsPlugin.cancelAll();
|
||||||
|
|
||||||
|
|
@ -165,8 +186,20 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var index = 0;
|
var index = 0;
|
||||||
|
var groupHistoryIndex = 0;
|
||||||
|
|
||||||
for (final msg in newMessages) {
|
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;
|
index += 1;
|
||||||
if (msg.type == MessageType.text &&
|
if (msg.type == MessageType.text &&
|
||||||
msg.senderId != null &&
|
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) {
|
for (final contactId in openedMessages.keys) {
|
||||||
await notifyContactAboutOpeningMessage(
|
await notifyContactAboutOpeningMessage(
|
||||||
|
|
@ -262,9 +300,9 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: GestureDetector(
|
title: GestureDetector(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (widget.group.isDirectChat) {
|
if (group.isDirectChat) {
|
||||||
final member = await twonlyDB.groupsDao
|
final member =
|
||||||
.getGroupMembers(widget.group.groupId);
|
await twonlyDB.groupsDao.getGroupMembers(group.groupId);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
await Navigator.push(
|
await Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|
@ -279,7 +317,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return GroupView(widget.group);
|
return GroupView(group);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -298,7 +336,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
substringBy(widget.group.groupName, 20),
|
substringBy(group.groupName, 20),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
VerifiedShield(key: verifyShieldKey, group: group),
|
VerifiedShield(key: verifyShieldKey, group: group),
|
||||||
|
|
@ -342,6 +380,8 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
|
} else if (messages[i].isGroupAction) {
|
||||||
|
return ChatGroupAction(action: messages[i].groupAction!);
|
||||||
} else {
|
} else {
|
||||||
final chatMessage = messages[i].message!;
|
final chatMessage = messages[i].message!;
|
||||||
return Transform.translate(
|
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(
|
return Container(
|
||||||
|
key: GlobalKey(),
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
minHeight: 2 * (widget.fontSize ?? 20),
|
minHeight: 2 * (widget.fontSize ?? 20),
|
||||||
minWidth: 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 {
|
class BetterListTile extends StatelessWidget {
|
||||||
const BetterListTile({
|
const BetterListTile({
|
||||||
required this.icon,
|
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
this.icon,
|
||||||
|
this.leading,
|
||||||
super.key,
|
super.key,
|
||||||
this.color,
|
this.color,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
|
this.trailing,
|
||||||
this.iconSize = 20,
|
this.iconSize = 20,
|
||||||
this.padding,
|
this.padding,
|
||||||
});
|
});
|
||||||
final IconData icon;
|
final IconData? icon;
|
||||||
|
final Widget? leading;
|
||||||
|
final Widget? trailing;
|
||||||
final String text;
|
final String text;
|
||||||
final Widget? subtitle;
|
final Widget? subtitle;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
|
|
@ -30,12 +34,15 @@ class BetterListTile extends StatelessWidget {
|
||||||
left: 19,
|
left: 19,
|
||||||
)
|
)
|
||||||
: padding!,
|
: padding!,
|
||||||
child: FaIcon(
|
child: (leading != null)
|
||||||
icon,
|
? leading
|
||||||
size: iconSize,
|
: FaIcon(
|
||||||
color: color,
|
icon,
|
||||||
),
|
size: iconSize,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
trailing: trailing,
|
||||||
title: Text(
|
title: Text(
|
||||||
text,
|
text,
|
||||||
style: TextStyle(color: color),
|
style: TextStyle(color: color),
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class _ContextMenuState extends State<ContextMenu> {
|
||||||
elevation: 1,
|
elevation: 1,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12), // corner radius
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
popUpAnimationStyle: const AnimationStyle(
|
popUpAnimationStyle: const AnimationStyle(
|
||||||
duration: Duration.zero,
|
duration: Duration.zero,
|
||||||
|
|
@ -56,7 +56,7 @@ class _ContextMenuState extends State<ContextMenu> {
|
||||||
items: <PopupMenuEntry<int>>[
|
items: <PopupMenuEntry<int>>[
|
||||||
...widget.items.map(
|
...widget.items.map(
|
||||||
(item) => PopupMenuItem(
|
(item) => PopupMenuItem(
|
||||||
padding: EdgeInsets.zero,
|
padding: const EdgeInsets.only(right: 4),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(item.title),
|
title: Text(item.title),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,18 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'package:flutter/material.dart';
|
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/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 {
|
class GroupView extends StatefulWidget {
|
||||||
const GroupView(this.group, {super.key});
|
const GroupView(this.group, {super.key});
|
||||||
|
|
@ -11,8 +24,211 @@ class GroupView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GroupViewState extends State<GroupView> {
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
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