mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 12:48:41 +00:00
add message action table
This commit is contained in:
parent
645dfe16da
commit
1c154e6c67
18 changed files with 997 additions and 479 deletions
|
|
@ -97,6 +97,10 @@ class ContactsDao extends DatabaseAccessor<TwonlyDB> with _$ContactsDaoMixin {
|
||||||
return select(contacts)..where((t) => t.userId.equals(userId));
|
return select(contacts)..where((t) => t.userId.equals(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Contact>> getContactsByUsername(String username) async {
|
||||||
|
return (select(contacts)..where((t) => t.username.equals(username))).get();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> deleteContactByUserId(int userId) {
|
Future<void> deleteContactByUserId(int userId) {
|
||||||
return (delete(contacts)..where((t) => t.userId.equals(userId))).go();
|
return (delete(contacts)..where((t) => t.userId.equals(userId))).go();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,17 @@ class GroupsDao extends DatabaseAccessor<TwonlyDB> with _$GroupsDaoMixin {
|
||||||
return (select(groupMembers)..where((t) => t.groupId.equals(groupId)))
|
return (select(groupMembers)..where((t) => t.groupId.equals(groupId)))
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<Group>> getDirectChat(int userId) async {
|
||||||
|
final query = (select(groups).join([
|
||||||
|
leftOuterJoin(
|
||||||
|
groupMembers,
|
||||||
|
groupMembers.groupId.equalsExp(groups.groupId) &
|
||||||
|
groupMembers.contactId.equals(userId),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
..where(groups.isGroupOfTwo.equals(true)));
|
||||||
|
|
||||||
|
return query.map((row) => row.readTable(groups)).get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ part 'messages.dao.g.dart';
|
||||||
Contacts,
|
Contacts,
|
||||||
MediaFiles,
|
MediaFiles,
|
||||||
MessageHistories,
|
MessageHistories,
|
||||||
|
MessageActions,
|
||||||
Groups,
|
Groups,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
@ -192,11 +193,10 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
(t) => t.messageId.equals(messageId) & t.senderId.equals(contactId),
|
(t) => t.messageId.equals(messageId) & t.senderId.equals(contactId),
|
||||||
))
|
))
|
||||||
.write(
|
.write(
|
||||||
MessagesCompanion(
|
const MessagesCompanion(
|
||||||
isDeletedFromSender: const Value(true),
|
isDeletedFromSender: Value(true),
|
||||||
content: const Value(null),
|
content: Value(null),
|
||||||
modifiedAt: Value(timestamp),
|
mediaId: Value(null),
|
||||||
mediaId: const Value(null),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -215,6 +215,7 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
MessageHistoriesCompanion(
|
MessageHistoriesCompanion(
|
||||||
messageId: Value(messageId),
|
messageId: Value(messageId),
|
||||||
content: Value(msg.content),
|
content: Value(msg.content),
|
||||||
|
createdAt: Value(timestamp),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await (update(messages)
|
await (update(messages)
|
||||||
|
|
@ -224,29 +225,36 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
.write(
|
.write(
|
||||||
MessagesCompanion(
|
MessagesCompanion(
|
||||||
content: Value(text),
|
content: Value(text),
|
||||||
modifiedAt: Value(timestamp),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleMessageOpened(
|
Future<void> handleMessageOpened(
|
||||||
String groupId,
|
int contactId,
|
||||||
String messageId,
|
String messageId,
|
||||||
DateTime timestamp,
|
DateTime timestamp,
|
||||||
) async {
|
) async {
|
||||||
final msg = await getMessageById(messageId).getSingleOrNull();
|
await into(messageActions).insert(
|
||||||
if (msg == null) return;
|
MessageActionsCompanion(
|
||||||
await (update(messages)
|
messageId: Value(messageId),
|
||||||
..where(
|
contactId: Value(contactId),
|
||||||
(t) =>
|
type: const Value(MessageActionType.ackByUserAt),
|
||||||
t.groupId.equals(groupId) &
|
actionAt: Value(timestamp),
|
||||||
t.messageId.equals(messageId) &
|
),
|
||||||
t.senderId.isNull(),
|
);
|
||||||
))
|
}
|
||||||
.write(
|
|
||||||
MessagesCompanion(
|
Future<void> handleMessageAckByServer(
|
||||||
openedAt: Value(timestamp),
|
int contactId,
|
||||||
openedByCounter: Value(msg.openedByCounter + 1),
|
String messageId,
|
||||||
|
DateTime timestamp,
|
||||||
|
) async {
|
||||||
|
await into(messageActions).insert(
|
||||||
|
MessageActionsCompanion(
|
||||||
|
messageId: Value(messageId),
|
||||||
|
contactId: Value(contactId),
|
||||||
|
type: const Value(MessageActionType.ackByServerAt),
|
||||||
|
actionAt: Value(timestamp),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,5 @@ mixin _$MessagesDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||||
$MessagesTable get messages => attachedDatabase.messages;
|
$MessagesTable get messages => attachedDatabase.messages;
|
||||||
$MessageHistoriesTable get messageHistories =>
|
$MessageHistoriesTable get messageHistories =>
|
||||||
attachedDatabase.messageHistories;
|
attachedDatabase.messageHistories;
|
||||||
|
$MessageActionsTable get messageActions => attachedDatabase.messageActions;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
part 'receipts.dao.g.dart';
|
part 'receipts.dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [Receipts, Messages])
|
@DriftAccessor(tables: [Receipts, Messages, MessageActions])
|
||||||
class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
// this constructor is required so that the main database can create an instance
|
// this constructor is required so that the main database can create an instance
|
||||||
// of this object.
|
// of this object.
|
||||||
|
|
@ -24,11 +24,11 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
if (receipt == null) return;
|
if (receipt == null) return;
|
||||||
|
|
||||||
if (receipt.messageId != null) {
|
if (receipt.messageId != null) {
|
||||||
await (update(messages)
|
await into(messageActions).insert(
|
||||||
..where((t) => t.messageId.equals(receipt.messageId!)))
|
MessageActionsCompanion(
|
||||||
.write(
|
messageId: Value(receipt.messageId!),
|
||||||
const MessagesCompanion(
|
contactId: Value(fromUserId),
|
||||||
ackByUser: Value(true),
|
type: const Value(MessageActionType.ackByUserAt),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +81,10 @@ class ReceiptsDao extends DatabaseAccessor<TwonlyDB> with _$ReceiptsDaoMixin {
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream<List<Receipt>> watchAll() {
|
||||||
|
return select(receipts).watch();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateReceipt(
|
Future<void> updateReceipt(
|
||||||
String receiptId,
|
String receiptId,
|
||||||
ReceiptsCompanion updates,
|
ReceiptsCompanion updates,
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,5 @@ mixin _$ReceiptsDaoMixin on DatabaseAccessor<TwonlyDB> {
|
||||||
$MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles;
|
$MediaFilesTable get mediaFiles => attachedDatabase.mediaFiles;
|
||||||
$MessagesTable get messages => attachedDatabase.messages;
|
$MessagesTable get messages => attachedDatabase.messages;
|
||||||
$ReceiptsTable get receipts => attachedDatabase.receipts;
|
$ReceiptsTable get receipts => attachedDatabase.receipts;
|
||||||
|
$MessageActionsTable get messageActions => attachedDatabase.messageActions;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,28 +30,50 @@ class Messages extends Table {
|
||||||
|
|
||||||
BoolColumn get isEdited => boolean().withDefault(const Constant(false))();
|
BoolColumn get isEdited => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
BoolColumn get ackByUser => boolean().withDefault(const Constant(false))();
|
|
||||||
BoolColumn get ackByServer => boolean().withDefault(const Constant(false))();
|
|
||||||
|
|
||||||
IntColumn get openedByCounter => integer().withDefault(const Constant(0))();
|
|
||||||
DateTimeColumn get openedAt => dateTime().nullable()();
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
DateTimeColumn get modifiedAt =>
|
|
||||||
dateTime().nullable().withDefault(currentDateAndTime)();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {messageId};
|
Set<Column> get primaryKey => {messageId};
|
||||||
}
|
}
|
||||||
|
|
||||||
@DataClassName('MessageHistory')
|
enum MessageActionType {
|
||||||
class MessageHistories extends Table {
|
openedAt,
|
||||||
|
modifiedAt,
|
||||||
|
ackByUserAt,
|
||||||
|
ackByServerAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataClassName('MessageAction')
|
||||||
|
class MessageActions extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
|
||||||
TextColumn get messageId =>
|
TextColumn get messageId =>
|
||||||
text().references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
text().references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
IntColumn get contactId =>
|
||||||
|
integer().references(Contacts, #contactId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
TextColumn get type => textEnum<MessageActionType>()();
|
||||||
|
DateTimeColumn get actionAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
|
}
|
||||||
|
|
||||||
|
@DataClassName('MessageHistory')
|
||||||
|
class MessageHistories extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
|
||||||
|
TextColumn get messageId =>
|
||||||
|
text().references(Messages, #messageId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
IntColumn get contactId =>
|
||||||
|
integer().references(Contacts, #contactId, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
TextColumn get content => text().nullable()();
|
TextColumn get content => text().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {messageId, createdAt};
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ part 'twonly.db.g.dart';
|
||||||
SignalSessionStores,
|
SignalSessionStores,
|
||||||
SignalContactPreKeys,
|
SignalContactPreKeys,
|
||||||
SignalContactSignedPreKeys,
|
SignalContactSignedPreKeys,
|
||||||
|
MessageActions
|
||||||
],
|
],
|
||||||
daos: [
|
daos: [
|
||||||
MessagesDao,
|
MessagesDao,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -76,12 +76,22 @@ Future<void> handleUploadStatusUpdate(TaskStatusUpdate update) async {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await twonlyDB.messagesDao.updateMessagesByMediaId(
|
/// As the messages where send in a bulk acknowledge all messages.
|
||||||
media.mediaId,
|
|
||||||
const MessagesCompanion(
|
final messages =
|
||||||
ackByServer: Value(true),
|
await twonlyDB.messagesDao.getMessagesByMediaId(media.mediaId);
|
||||||
),
|
for (final message in messages) {
|
||||||
|
final contacts =
|
||||||
|
await twonlyDB.groupsDao.getGroupMembers(message.groupId);
|
||||||
|
for (final contact in contacts) {
|
||||||
|
await twonlyDB.messagesDao.handleMessageAckByServer(
|
||||||
|
contact.contactId,
|
||||||
|
message.messageId,
|
||||||
|
DateTime.now(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.error(
|
Log.error(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
import 'package:libsignal_protocol_dart/libsignal_protocol_dart.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
|
@ -126,11 +127,10 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
|
|
||||||
if (resp.isSuccess) {
|
if (resp.isSuccess) {
|
||||||
if (receipt.messageId != null) {
|
if (receipt.messageId != null) {
|
||||||
await twonlyDB.messagesDao.updateMessageId(
|
await twonlyDB.messagesDao.handleMessageAckByServer(
|
||||||
|
receipt.contactId,
|
||||||
receipt.messageId!,
|
receipt.messageId!,
|
||||||
const MessagesCompanion(
|
DateTime.now(),
|
||||||
ackByServer: Value(true),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!receipt.contactWillSendsReceipt) {
|
if (!receipt.contactWillSendsReceipt) {
|
||||||
|
|
@ -158,6 +158,37 @@ Future<(Uint8List, Uint8List?)?> tryToSendCompleteMessage({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> insertAndSendTextMessage(
|
||||||
|
String groupId,
|
||||||
|
String textMessage,
|
||||||
|
) async {
|
||||||
|
final message = await twonlyDB.messagesDao.insertMessage(
|
||||||
|
MessagesCompanion(
|
||||||
|
groupId: Value(groupId),
|
||||||
|
content: Value(textMessage),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (message == null) {
|
||||||
|
Log.error('Could not insert message into database');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final groupMembers = await twonlyDB.groupsDao.getGroupMembers(groupId);
|
||||||
|
|
||||||
|
for (final groupMember in groupMembers) {
|
||||||
|
unawaited(sendCipherText(
|
||||||
|
groupMember.contactId,
|
||||||
|
pb.EncryptedContent(
|
||||||
|
textMessage: pb.EncryptedContent_TextMessage(
|
||||||
|
senderMessageId: message.messageId,
|
||||||
|
text: textMessage,
|
||||||
|
timestamp: Int64(message.createdAt.millisecondsSinceEpoch),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<(Uint8List, Uint8List?)?> sendCipherText(
|
Future<(Uint8List, Uint8List?)?> sendCipherText(
|
||||||
int contactId,
|
int contactId,
|
||||||
pb.EncryptedContent encryptedContent, {
|
pb.EncryptedContent encryptedContent, {
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,6 @@ Future<PlaintextContent?> handleEncryptedMessage(
|
||||||
if (content.hasMessageUpdate()) {
|
if (content.hasMessageUpdate()) {
|
||||||
await handleMessageUpdate(
|
await handleMessageUpdate(
|
||||||
fromUserId,
|
fromUserId,
|
||||||
content.groupId,
|
|
||||||
content.messageUpdate,
|
content.messageUpdate,
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,6 @@ Future<void> handleMedia(
|
||||||
senderId: Value(fromUserId),
|
senderId: Value(fromUserId),
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
mediaId: Value(mediaFile.mediaId),
|
mediaId: Value(mediaFile.mediaId),
|
||||||
ackByServer: const Value(true),
|
|
||||||
ackByUser: const Value(true),
|
|
||||||
quotesMessageId: Value(
|
quotesMessageId: Value(
|
||||||
media.hasQuoteMessageId() ? media.quoteMessageId : null,
|
media.hasQuoteMessageId() ? media.quoteMessageId : null,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
Future<void> handleMessageUpdate(
|
Future<void> handleMessageUpdate(
|
||||||
int contactId,
|
int contactId,
|
||||||
String groupId,
|
|
||||||
EncryptedContent_MessageUpdate messageUpdate,
|
EncryptedContent_MessageUpdate messageUpdate,
|
||||||
) async {
|
) async {
|
||||||
switch (messageUpdate.type) {
|
switch (messageUpdate.type) {
|
||||||
|
|
@ -14,7 +13,7 @@ Future<void> handleMessageUpdate(
|
||||||
'Opened message ${messageUpdate.multipleSenderMessageIds.length}');
|
'Opened message ${messageUpdate.multipleSenderMessageIds.length}');
|
||||||
for (final senderMessageId in messageUpdate.multipleSenderMessageIds) {
|
for (final senderMessageId in messageUpdate.multipleSenderMessageIds) {
|
||||||
await twonlyDB.messagesDao.handleMessageOpened(
|
await twonlyDB.messagesDao.handleMessageOpened(
|
||||||
groupId,
|
contactId,
|
||||||
senderMessageId,
|
senderMessageId,
|
||||||
fromTimestamp(messageUpdate.timestamp),
|
fromTimestamp(messageUpdate.timestamp),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ Future<void> handleTextMessage(
|
||||||
senderId: Value(fromUserId),
|
senderId: Value(fromUserId),
|
||||||
groupId: Value(groupId),
|
groupId: Value(groupId),
|
||||||
content: Value(textMessage.text),
|
content: Value(textMessage.text),
|
||||||
ackByServer: const Value(true),
|
|
||||||
ackByUser: const Value(true),
|
|
||||||
quotesMessageId: Value(
|
quotesMessageId: Value(
|
||||||
textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
|
textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/model/json/message_old.dart';
|
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
||||||
class AutomatedTestingView extends StatefulWidget {
|
class AutomatedTestingView extends StatefulWidget {
|
||||||
const AutomatedTestingView({super.key});
|
const AutomatedTestingView({super.key});
|
||||||
|
|
@ -36,27 +36,29 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
|
||||||
title: const Text('Sending a lot of messages.'),
|
title: const Text('Sending a lot of messages.'),
|
||||||
subtitle: Text(lotsOfMessagesStatus),
|
subtitle: Text(lotsOfMessagesStatus),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await twonlyDB.messageRetransmissionDao
|
final username = await showUserNameDialog(context);
|
||||||
.clearRetransmissionTable();
|
if (username == null) return;
|
||||||
|
|
||||||
final contacts =
|
final contacts =
|
||||||
await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
await twonlyDB.contactsDao.getContactsByUsername(username);
|
||||||
|
|
||||||
for (final contact in contacts) {
|
for (final contact in contacts) {
|
||||||
|
final groups =
|
||||||
|
await twonlyDB.groupsDao.getDirectChat(contact.userId);
|
||||||
|
|
||||||
|
for (final group in groups) {
|
||||||
for (var i = 0; i < 200; i++) {
|
for (var i = 0; i < 200; i++) {
|
||||||
setState(() {
|
setState(() {
|
||||||
lotsOfMessagesStatus =
|
lotsOfMessagesStatus =
|
||||||
'At message $i to ${contact.username}.';
|
'At message $i to ${contact.username}.';
|
||||||
});
|
});
|
||||||
await sendTextMessage(
|
await insertAndSendTextMessage(
|
||||||
contact.userId,
|
group.groupId,
|
||||||
TextMessageContent(
|
'Message $i.',
|
||||||
text: 'TestMessage $i',
|
|
||||||
),
|
|
||||||
null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -64,3 +66,37 @@ class _AutomatedTestingViewState extends State<AutomatedTestingView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> showUserNameDialog(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
|
||||||
|
return showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Username'),
|
||||||
|
content: TextField(
|
||||||
|
controller: controller,
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: Text(context.lang.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(); // Close the dialog
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text(context.lang.ok),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop(controller.text); // Return the input text
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart' hide Column;
|
|
||||||
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/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/model/json/message_old.dart';
|
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
|
||||||
|
|
||||||
class RetransmissionDataView extends StatefulWidget {
|
class RetransmissionDataView extends StatefulWidget {
|
||||||
const RetransmissionDataView({super.key});
|
const RetransmissionDataView({super.key});
|
||||||
|
|
@ -19,33 +12,22 @@ class RetransmissionDataView extends StatefulWidget {
|
||||||
|
|
||||||
class RetransMsg {
|
class RetransMsg {
|
||||||
RetransMsg({
|
RetransMsg({
|
||||||
required this.json,
|
required this.receipt,
|
||||||
required this.retrans,
|
|
||||||
required this.contact,
|
required this.contact,
|
||||||
});
|
});
|
||||||
final MessageJson json;
|
final Receipt receipt;
|
||||||
final MessageRetransmission retrans;
|
|
||||||
final Contact? contact;
|
final Contact? contact;
|
||||||
|
|
||||||
static List<RetransMsg> fromRaw(
|
static List<RetransMsg> fromRaw(
|
||||||
List<MessageRetransmission> retrans,
|
List<Receipt> receipts,
|
||||||
Map<int, Contact> contacts,
|
Map<int, Contact> contacts,
|
||||||
) {
|
) {
|
||||||
final res = <RetransMsg>[];
|
final res = <RetransMsg>[];
|
||||||
|
for (final receipt in receipts) {
|
||||||
for (final retrans in retrans) {
|
|
||||||
final json = MessageJson.fromJson(
|
|
||||||
jsonDecode(
|
|
||||||
utf8.decode(
|
|
||||||
gzip.decode(retrans.plaintextContent),
|
|
||||||
),
|
|
||||||
) as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
res.add(
|
res.add(
|
||||||
RetransMsg(
|
RetransMsg(
|
||||||
json: json,
|
receipt: receipt,
|
||||||
retrans: retrans,
|
contact: contacts[receipt.contactId],
|
||||||
contact: contacts[retrans.contactId],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -54,9 +36,9 @@ class RetransMsg {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RetransmissionDataViewState extends State<RetransmissionDataView> {
|
class _RetransmissionDataViewState extends State<RetransmissionDataView> {
|
||||||
List<MessageRetransmission> retransmissions = [];
|
List<Receipt> retransmissions = [];
|
||||||
Map<int, Contact> contacts = {};
|
Map<int, Contact> contacts = {};
|
||||||
StreamSubscription<List<MessageRetransmission>>? subscriptionRetransmission;
|
StreamSubscription<List<Receipt>>? subscriptionRetransmission;
|
||||||
StreamSubscription<List<Contact>>? subscriptionContacts;
|
StreamSubscription<List<Contact>>? subscriptionContacts;
|
||||||
List<RetransMsg> messages = [];
|
List<RetransMsg> messages = [];
|
||||||
|
|
||||||
|
|
@ -85,7 +67,7 @@ class _RetransmissionDataViewState extends State<RetransmissionDataView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
subscriptionRetransmission =
|
subscriptionRetransmission =
|
||||||
twonlyDB.messageRetransmissionDao.watchAllMessages().listen((updated) {
|
twonlyDB.receiptsDao.watchAll().listen((updated) {
|
||||||
retransmissions = updated;
|
retransmissions = updated;
|
||||||
if (contacts.isNotEmpty) {
|
if (contacts.isNotEmpty) {
|
||||||
messages = RetransMsg.fromRaw(retransmissions, contacts);
|
messages = RetransMsg.fromRaw(retransmissions, contacts);
|
||||||
|
|
@ -108,7 +90,7 @@ class _RetransmissionDataViewState extends State<RetransmissionDataView> {
|
||||||
.map(
|
.map(
|
||||||
(retrans) => ListTile(
|
(retrans) => ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
'${retrans.retrans.retransmissionId}: ${retrans.json.kind}',
|
retrans.receipt.receiptId,
|
||||||
),
|
),
|
||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -117,64 +99,13 @@ class _RetransmissionDataViewState extends State<RetransmissionDataView> {
|
||||||
'To ${retrans.contact?.username}',
|
'To ${retrans.contact?.username}',
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Server-Ack: ${retrans.retrans.acknowledgeByServerAt}',
|
'Server-Ack: ${retrans.receipt.ackByServerAt}',
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Retry: ${retrans.retrans.retryCount} : ${retrans.retrans.lastRetry}',
|
'Retry: ${retrans.receipt.retryCount} : ${retrans.receipt.lastRetry}',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
trailing: SizedBox(
|
|
||||||
width: 80,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 20,
|
|
||||||
width: 40,
|
|
||||||
child: Center(
|
|
||||||
child: GestureDetector(
|
|
||||||
onDoubleTap: () async {
|
|
||||||
await twonlyDB.messageRetransmissionDao
|
|
||||||
.deleteRetransmissionById(
|
|
||||||
retrans.retrans.retransmissionId,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const FaIcon(
|
|
||||||
FontAwesomeIcons.trash,
|
|
||||||
size: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 40,
|
|
||||||
child: OutlinedButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: WidgetStateProperty.all<EdgeInsets>(
|
|
||||||
EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
await twonlyDB.messageRetransmissionDao
|
|
||||||
.updateRetransmission(
|
|
||||||
retrans.retrans.retransmissionId,
|
|
||||||
const MessageRetransmissionsCompanion(
|
|
||||||
acknowledgeByServerAt: Value(null),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await sendRetransmitMessage(
|
|
||||||
retrans.retrans.retransmissionId,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const FaIcon(
|
|
||||||
FontAwesomeIcons.arrowRotateLeft,
|
|
||||||
size: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
||||||
import 'package:twonly/src/services/api/mediafiles/upload.service.dart'
|
|
||||||
show createDownloadToken, uint8ListToHex;
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/settings/help/contact_us/submit_message.view.dart';
|
import 'package:twonly/src/views/settings/help/contact_us/submit_message.view.dart';
|
||||||
|
|
@ -32,7 +30,7 @@ class _ContactUsState extends State<ContactUsView> {
|
||||||
|
|
||||||
Future<String?> uploadDebugLog() async {
|
Future<String?> uploadDebugLog() async {
|
||||||
if (debugLogDownloadToken != null) return debugLogDownloadToken;
|
if (debugLogDownloadToken != null) return debugLogDownloadToken;
|
||||||
final downloadToken = createDownloadToken();
|
final downloadToken = getRandomUint8List(32);
|
||||||
|
|
||||||
final debugLog = await loadLogFile();
|
final debugLog = await loadLogFile();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue