twonly-app/lib/src/database/daos/groups.dao.dart
2025-11-05 23:12:10 +01:00

376 lines
12 KiB
Dart

import 'package:drift/drift.dart';
import 'package:hashlib/random.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/log.dart';
import 'package:twonly/src/utils/misc.dart';
part 'groups.dao.g.dart';
@DriftAccessor(
tables: [
Groups,
GroupMembers,
GroupHistories,
],
)
class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
// this constructor is required so that the main database can create an instance
// of this object.
// ignore: matching_super_parameters
GroupsDao(super.db);
Future<bool> isContactInGroup(int contactId, String groupId) async {
final entry = await (select(groupMembers)..where(
// ignore: require_trailing_commas
(t) => t.contactId.equals(contactId) & t.groupId.equals(groupId)))
.getSingleOrNull();
return entry != null;
}
Future<void> deleteGroup(String groupId) async {
await (delete(groups)..where((t) => t.groupId.equals(groupId))).go();
}
Future<void> updateGroup(
String groupId,
GroupsCompanion updates,
) async {
await (update(groups)..where((c) => c.groupId.equals(groupId)))
.write(updates);
}
Future<List<GroupMember>> getGroupNonLeftMembers(String groupId) async {
return (select(groupMembers)
..where(
(t) =>
t.groupId.equals(groupId) &
(t.memberState.equals(MemberState.leftGroup.name).not() |
t.memberState.isNull()),
))
.get();
}
Future<List<GroupMember>> getAllGroupMembers(String groupId) async {
return (select(groupMembers)..where((t) => t.groupId.equals(groupId)))
.get();
}
Future<GroupMember?> getGroupMemberByPublicKey(Uint8List publicKey) async {
return (select(groupMembers)
..where((t) => t.groupPublicKey.equals(publicKey)))
.getSingleOrNull();
}
Future<Group?> createNewGroup(GroupsCompanion group) async {
return _insertGroup(group);
}
Future<void> insertOrUpdateGroupMember(GroupMembersCompanion members) async {
await into(groupMembers).insertOnConflictUpdate(members);
}
Future<void> insertGroupAction(GroupHistoriesCompanion action) async {
var insertAction = action;
if (!action.groupHistoryId.present) {
insertAction = action.copyWith(
groupHistoryId: Value(uuid.v4()),
);
}
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,
GroupMembersCompanion updates,
) async {
await (update(groupMembers)
..where(
(c) => c.groupId.equals(groupId) & c.contactId.equals(contactId),
))
.write(updates);
}
Future<void> removeMember(String groupId, int contactId) async {
await (delete(groupMembers)
..where(
(c) => c.groupId.equals(groupId) & c.contactId.equals(contactId),
))
.go();
}
Future<Group?> createNewDirectChat(
int contactId,
GroupsCompanion group,
) async {
final groupIdDirectChat = getUUIDforDirectChat(contactId, gUser.userId);
final insertGroup = group.copyWith(
groupId: Value(groupIdDirectChat),
isDirectChat: const Value(true),
isGroupAdmin: const Value(true),
joinedGroup: const Value(true),
);
final result = await _insertGroup(insertGroup);
if (result != null) {
await into(groupMembers).insert(
GroupMembersCompanion(
groupId: Value(result.groupId),
contactId: Value(
contactId,
),
),
);
}
return result;
}
Future<Group?> _insertGroup(GroupsCompanion group) async {
try {
await into(groups).insert(group);
return await (select(groups)
..where((t) => t.groupId.equals(group.groupId.value)))
.getSingle();
} catch (e) {
Log.error('Could not insert group: $e');
return null;
}
}
Future<List<Contact>> getGroupContact(String groupId) async {
final query = (select(contacts).join([
leftOuterJoin(
groupMembers,
groupMembers.contactId.equalsExp(contacts.userId),
useColumns: false,
),
])
..orderBy([OrderingTerm.desc(groupMembers.lastMessage)])
..where(groupMembers.groupId.equals(groupId)));
return query.map((row) => row.readTable(contacts)).get();
}
Stream<List<Contact>> watchGroupContact(String groupId) {
final query = (select(contacts).join([
leftOuterJoin(
groupMembers,
groupMembers.contactId.equalsExp(contacts.userId),
useColumns: false,
),
])
..orderBy([OrderingTerm.desc(groupMembers.lastMessage)])
..where(groupMembers.groupId.equals(groupId)));
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>> watchGroupsForShareImage() {
return (select(groups)
..where(
(g) => g.leftGroup.equals(false) & g.deletedContent.equals(false),
))
.watch();
}
Stream<Group?> watchGroup(String groupId) {
return (select(groups)..where((t) => t.groupId.equals(groupId)))
.watchSingleOrNull();
}
Stream<List<Group>> watchGroupsForChatList() {
return (select(groups)
..where((t) => t.deletedContent.equals(false))
..orderBy([(t) => OrderingTerm.desc(t.lastMessageExchange)]))
.watch();
}
Stream<List<Group>> watchGroupsForStartNewChat() {
return (select(groups)
..where((t) => t.isDirectChat.equals(false))
..orderBy([(t) => OrderingTerm.asc(t.groupName)]))
.watch();
}
Future<Group?> getGroup(String groupId) {
return (select(groups)..where((t) => t.groupId.equals(groupId)))
.getSingleOrNull();
}
Stream<int> watchFlameCounter(String groupId) {
return (select(groups)
..where(
(u) =>
u.groupId.equals(groupId) &
u.lastMessageReceived.isNotNull() &
u.lastMessageSend.isNotNull(),
))
.watchSingleOrNull()
.asyncMap(getFlameCounterFromGroup);
}
Future<List<Group>> getAllDirectChats() {
return (select(groups)..where((t) => t.isDirectChat.equals(true))).get();
}
Future<List<Group>> getAllNotJoinedGroups() {
return (select(groups)
..where(
(t) => t.joinedGroup.equals(false) & t.isDirectChat.equals(false),
))
.get();
}
Future<List<GroupMember>> getAllGroupMemberWithoutPublicKey() {
final query =
((select(groups)..where((t) => t.isDirectChat.equals(false))).join([
leftOuterJoin(
groupMembers,
groupMembers.groupId.equalsExp(groups.groupId),
),
])
..where(groupMembers.groupPublicKey.isNull()));
return query.map((row) => row.readTable(groupMembers)).get();
}
Future<Group?> getDirectChat(int userId) async {
final query =
((select(groups)..where((t) => t.isDirectChat.equals(true))).join([
leftOuterJoin(
groupMembers,
groupMembers.groupId.equalsExp(groups.groupId),
),
])
..where(groupMembers.contactId.equals(userId)));
return query.map((row) => row.readTable(groups)).getSingleOrNull();
}
Future<void> incFlameCounter(
String groupId,
bool received,
DateTime timestamp,
) async {
final group = await (select(groups)
..where((t) => t.groupId.equals(groupId)))
.getSingle();
final totalMediaCounter = group.totalMediaCounter + 1;
var flameCounter = group.flameCounter;
var maxFlameCounter = group.maxFlameCounter;
var maxFlameCounterFrom = group.maxFlameCounterFrom;
if (group.lastMessageReceived != null && group.lastMessageSend != null) {
final now = DateTime.now();
final startOfToday = DateTime(now.year, now.month, now.day);
final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
if (group.lastMessageSend!.isBefore(twoDaysAgo) ||
group.lastMessageReceived!.isBefore(twoDaysAgo)) {
flameCounter = 0;
}
}
var lastMessageSend = const Value<DateTime?>.absent();
var lastMessageReceived = const Value<DateTime?>.absent();
var lastFlameCounterChange = const Value<DateTime?>.absent();
if (group.lastFlameCounterChange != null) {
final now = DateTime.now();
final startOfToday = DateTime(now.year, now.month, now.day);
if (group.lastFlameCounterChange!.isBefore(startOfToday)) {
// last flame update was yesterday. check if it can be updated.
var updateFlame = false;
if (received) {
if (group.lastMessageSend != null &&
group.lastMessageSend!.isAfter(startOfToday)) {
// today a message was already send -> update flame
updateFlame = true;
}
} else if (group.lastMessageReceived != null &&
group.lastMessageReceived!.isAfter(startOfToday)) {
// today a message was already received -> update flame
updateFlame = true;
}
if (updateFlame) {
flameCounter += 1;
lastFlameCounterChange = Value(timestamp);
if (flameCounter > maxFlameCounter) {
maxFlameCounter = flameCounter;
maxFlameCounterFrom = DateTime.now();
}
}
}
} else {
// There where no message until no...
lastFlameCounterChange = Value(timestamp);
}
if (received) {
lastMessageReceived = Value(timestamp);
} else {
lastMessageSend = Value(timestamp);
}
await (update(groups)..where((t) => t.groupId.equals(groupId))).write(
GroupsCompanion(
totalMediaCounter: Value(totalMediaCounter),
lastFlameCounterChange: lastFlameCounterChange,
lastMessageReceived: lastMessageReceived,
lastMessageSend: lastMessageSend,
flameCounter: Value(flameCounter),
maxFlameCounter: Value(maxFlameCounter),
maxFlameCounterFrom: Value(maxFlameCounterFrom),
),
);
}
Future<void> increaseLastMessageExchange(
String groupId,
DateTime newLastMessage,
) async {
await (update(groups)
..where(
(t) =>
t.groupId.equals(groupId) &
(t.lastMessageExchange.isSmallerThanValue(newLastMessage)),
))
.write(GroupsCompanion(lastMessageExchange: Value(newLastMessage)));
}
}
int getFlameCounterFromGroup(Group? group) {
if (group == null) return 0;
if (group.lastMessageSend == null || group.lastMessageReceived == null) {
return 0;
}
final now = DateTime.now();
final startOfToday = DateTime(now.year, now.month, now.day);
final twoDaysAgo = startOfToday.subtract(const Duration(days: 2));
if (group.lastMessageSend!.isAfter(twoDaysAgo) &&
group.lastMessageReceived!.isAfter(twoDaysAgo)) {
return group.flameCounter + 1;
} else {
return 0;
}
}