mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-10-09 02:54:14 +00:00
341 lines
10 KiB
Dart
341 lines
10 KiB
Dart
import 'package:clock/clock.dart' show clock;
|
|
import 'package:drift/drift.dart';
|
|
import 'package:hashlib/random.dart';
|
|
import 'package:twonly/core/bridge.dart' show FlameState;
|
|
import 'package:twonly/core/bridge/groups.dart' as rust_groups;
|
|
import 'package:twonly/locator.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';
|
|
|
|
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<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<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, {
|
|
DateTime? since,
|
|
}) {
|
|
return (select(groupHistories)
|
|
..where(
|
|
(t) =>
|
|
t.groupId.equals(groupId) &
|
|
(since == null
|
|
? const Constant(true)
|
|
: t.actionAt.isBiggerOrEqualValue(since)),
|
|
)
|
|
..orderBy([(t) => OrderingTerm.asc(t.actionAt)]))
|
|
.watch();
|
|
}
|
|
|
|
Future<Group?> createNewDirectChat(
|
|
int contactId,
|
|
GroupsCompanion group,
|
|
) async {
|
|
final groupIdDirectChat = getUUIDforDirectChat(
|
|
contactId,
|
|
userService.currentUser.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).insertOnConflictUpdate(
|
|
GroupMembersCompanion(
|
|
groupId: Value(result.groupId),
|
|
contactId: Value(
|
|
contactId,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<Group?> _insertGroup(GroupsCompanion group) async {
|
|
await into(groups).insertOnConflictUpdate(group);
|
|
return (select(
|
|
groups,
|
|
)..where((t) => t.groupId.equals(group.groupId.value))).getSingleOrNull();
|
|
}
|
|
|
|
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<(Contact, GroupMember)>> watchAllGroupMembers() {
|
|
final query = select(groupMembers).join([
|
|
innerJoin(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<List<Group>> watchGroupsAllowedForWidgetShare() {
|
|
final query =
|
|
select(groups).join([
|
|
innerJoin(
|
|
groupMembers,
|
|
groupMembers.groupId.equalsExp(groups.groupId),
|
|
),
|
|
innerJoin(
|
|
contacts,
|
|
contacts.userId.equalsExp(groupMembers.contactId),
|
|
),
|
|
])..where(
|
|
groups.isDirectChat.equals(true) &
|
|
groups.leftGroup.equals(false) &
|
|
groups.deletedContent.equals(false) &
|
|
contacts.widgetSharingAllowed.equals(true),
|
|
);
|
|
return query.map((row) => row.readTable(groups)).watch();
|
|
}
|
|
|
|
Stream<List<GroupMember>> watchContactGroupMember(int contactId) {
|
|
return (select(groupMembers)..where(
|
|
(g) => g.contactId.equals(contactId),
|
|
))
|
|
.watch();
|
|
}
|
|
|
|
Stream<List<GroupMember>> watchTypingGroupMembers() {
|
|
return (select(
|
|
groupMembers,
|
|
)..where((member) => member.lastTypeIndicator.isNotNull())).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();
|
|
}
|
|
|
|
/// Drift watches the row only to learn *when* to ask again: the counter
|
|
/// itself is derived in Rust, because it depends on the current day as much
|
|
/// as on the stored columns.
|
|
Stream<FlameState> watchFlameCounter(String groupId) {
|
|
return (select(groups)..where(
|
|
(u) =>
|
|
u.groupId.equals(groupId) &
|
|
u.lastMessageReceived.isNotNull() &
|
|
u.lastMessageSend.isNotNull(),
|
|
))
|
|
.watchSingleOrNull()
|
|
.asyncMap((group) {
|
|
if (group == null) {
|
|
return const FlameState(
|
|
counter: 0,
|
|
isExpiring: false,
|
|
isBestFriend: false,
|
|
);
|
|
}
|
|
return rust_groups.flameState(groupId: groupId);
|
|
});
|
|
}
|
|
|
|
Future<List<Group>> getAllDirectChats() {
|
|
return (select(groups)..where((t) => t.isDirectChat.equals(true))).get();
|
|
}
|
|
|
|
Future<List<Group>> getAllGroups() {
|
|
return select(groups).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();
|
|
}
|
|
|
|
Stream<int> watchSumTotalMediaCounter() {
|
|
final query = selectOnly(groups)
|
|
..addColumns([groups.totalMediaCounter.sum()]);
|
|
return query.watch().map((rows) {
|
|
final expr = rows.first.read(groups.totalMediaCounter.sum());
|
|
return expr ?? 0;
|
|
});
|
|
}
|
|
|
|
Future<void> increaseLastMessageExchange(
|
|
String groupId,
|
|
DateTime newLastMessage,
|
|
) async {
|
|
final now = clock.now();
|
|
final clampedLastMessage = newLastMessage.isAfter(now)
|
|
? now
|
|
: newLastMessage;
|
|
await (update(groups)..where(
|
|
(t) =>
|
|
t.groupId.equals(groupId) &
|
|
(t.lastMessageExchange.isSmallerThanValue(clampedLastMessage)),
|
|
))
|
|
.write(GroupsCompanion(lastMessageExchange: Value(clampedLastMessage)));
|
|
}
|
|
|
|
Future<void> increaseMemberLastMessage(
|
|
String groupId,
|
|
int contactId,
|
|
DateTime newLastMessage,
|
|
) async {
|
|
final now = clock.now();
|
|
final clampedLastMessage = newLastMessage.isAfter(now)
|
|
? now
|
|
: newLastMessage;
|
|
await (update(groupMembers)..where(
|
|
(t) =>
|
|
t.groupId.equals(groupId) &
|
|
t.contactId.equals(contactId) &
|
|
(t.lastMessage.isNull() |
|
|
t.lastMessage.isSmallerThanValue(clampedLastMessage)),
|
|
))
|
|
.write(GroupMembersCompanion(lastMessage: Value(clampedLastMessage)));
|
|
}
|
|
|
|
Stream<List<Group>> watchNonDirectGroupsForMember(int contactId) {
|
|
final query =
|
|
select(groups).join([
|
|
innerJoin(
|
|
groupMembers,
|
|
groupMembers.groupId.equalsExp(groups.groupId),
|
|
),
|
|
])..where(
|
|
groups.isDirectChat.equals(false) &
|
|
groupMembers.contactId.equals(contactId),
|
|
);
|
|
|
|
return query.map((row) => row.readTable(groups)).watch();
|
|
}
|
|
}
|