mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 12:48:41 +00:00
display message history
This commit is contained in:
parent
6bb18a5bd0
commit
3d5fc3e807
17 changed files with 492 additions and 122 deletions
|
|
@ -68,6 +68,24 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
.watch();
|
.watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stream<List<GroupMember>> watchMembersByGroupId(String groupId) {
|
||||||
|
// return (select(groupMembers)..where((t) => t.groupId.equals(groupId)))
|
||||||
|
// .watch();
|
||||||
|
// }
|
||||||
|
|
||||||
|
Stream<List<(GroupMember, Contact)>> watchMembersByGroupId(String groupId) {
|
||||||
|
final query = (select(groupMembers).join([
|
||||||
|
leftOuterJoin(
|
||||||
|
contacts,
|
||||||
|
contacts.userId.equalsExp(groupMembers.contactId),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
..where(groupMembers.groupId.equals(groupId)));
|
||||||
|
return query
|
||||||
|
.map((row) => (row.readTable(groupMembers), row.readTable(contacts)))
|
||||||
|
.watch();
|
||||||
|
}
|
||||||
|
|
||||||
Stream<List<MessageAction>> watchMessageActionChanges(String messageId) {
|
Stream<List<MessageAction>> watchMessageActionChanges(String messageId) {
|
||||||
return (select(messageActions)..where((t) => t.messageId.equals(messageId)))
|
return (select(messageActions)..where((t) => t.messageId.equals(messageId)))
|
||||||
.watch();
|
.watch();
|
||||||
|
|
@ -410,6 +428,26 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
||||||
return (select(messages)..where((t) => t.mediaId.equals(mediaId))).get();
|
return (select(messages)..where((t) => t.mediaId.equals(mediaId))).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Stream<List<(MessageAction, Contact)>> watchMessageActions(String messageId) {
|
||||||
|
final query = (select(messageActions).join([
|
||||||
|
leftOuterJoin(
|
||||||
|
contacts,
|
||||||
|
contacts.userId.equalsExp(messageActions.contactId),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
..where(messageActions.messageId.equals(messageId)));
|
||||||
|
return query
|
||||||
|
.map((row) => (row.readTable(messageActions), row.readTable(contacts)))
|
||||||
|
.watch();
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<List<MessageHistory>> watchMessageHistory(String messageId) {
|
||||||
|
return (select(messageHistories)
|
||||||
|
..where((t) => t.messageId.equals(messageId))
|
||||||
|
..orderBy([(t) => OrderingTerm.desc(t.createdAt)]))
|
||||||
|
.watch();
|
||||||
|
}
|
||||||
|
|
||||||
// Future<List<Message>> getMessagesByMediaUploadId(int mediaUploadId) async {
|
// Future<List<Message>> getMessagesByMediaUploadId(int mediaUploadId) async {
|
||||||
// return (select(messages)
|
// return (select(messages)
|
||||||
// ..where((t) => t.mediaUploadId.equals(mediaUploadId)))
|
// ..where((t) => t.mediaUploadId.equals(mediaUploadId)))
|
||||||
|
|
|
||||||
|
|
@ -345,5 +345,11 @@
|
||||||
"newDeviceRegistered": "Du hast dich auf einem anderen Gerät angemeldet. Daher wurdest du hier abgemeldet.",
|
"newDeviceRegistered": "Du hast dich auf einem anderen Gerät angemeldet. Daher wurdest du hier abgemeldet.",
|
||||||
"tabToRemoveEmoji": "Tippen um zu entfernen",
|
"tabToRemoveEmoji": "Tippen um zu entfernen",
|
||||||
"quotedMessageWasDeleted": "Die zitierte Nachricht wurde gelöscht.",
|
"quotedMessageWasDeleted": "Die zitierte Nachricht wurde gelöscht.",
|
||||||
"messageWasDeleted": "Nachricht wurde gelöscht."
|
"messageWasDeleted": "Nachricht wurde gelöscht.",
|
||||||
|
"sent": "Versendet",
|
||||||
|
"sentTo": "Zugestellt an",
|
||||||
|
"received": "Empfangen",
|
||||||
|
"opened": "Geöffnet",
|
||||||
|
"waitingForInternet": "Warten auf Internet",
|
||||||
|
"editHistory": "Bearbeitungshistorie"
|
||||||
}
|
}
|
||||||
|
|
@ -501,5 +501,11 @@
|
||||||
"newDeviceRegistered": "You have logged in on another device. You have therefore been logged out here.",
|
"newDeviceRegistered": "You have logged in on another device. You have therefore been logged out here.",
|
||||||
"tabToRemoveEmoji": "Tab to remove",
|
"tabToRemoveEmoji": "Tab to remove",
|
||||||
"quotedMessageWasDeleted": "The quoted message has been deleted.",
|
"quotedMessageWasDeleted": "The quoted message has been deleted.",
|
||||||
"messageWasDeleted": "Message has been deleted."
|
"messageWasDeleted": "Message has been deleted.",
|
||||||
|
"sent": "Delivered",
|
||||||
|
"sentTo": "Delivered to",
|
||||||
|
"received": "Received",
|
||||||
|
"opened": "Opened",
|
||||||
|
"waitingForInternet": "Waiting for internet",
|
||||||
|
"editHistory": "Edit history"
|
||||||
}
|
}
|
||||||
|
|
@ -2113,6 +2113,42 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Message has been deleted.'**
|
/// **'Message has been deleted.'**
|
||||||
String get messageWasDeleted;
|
String get messageWasDeleted;
|
||||||
|
|
||||||
|
/// No description provided for @sent.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Delivered'**
|
||||||
|
String get sent;
|
||||||
|
|
||||||
|
/// No description provided for @sentTo.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Delivered to'**
|
||||||
|
String get sentTo;
|
||||||
|
|
||||||
|
/// No description provided for @received.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Received'**
|
||||||
|
String get received;
|
||||||
|
|
||||||
|
/// No description provided for @opened.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Opened'**
|
||||||
|
String get opened;
|
||||||
|
|
||||||
|
/// No description provided for @waitingForInternet.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Waiting for internet'**
|
||||||
|
String get waitingForInternet;
|
||||||
|
|
||||||
|
/// No description provided for @editHistory.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Edit history'**
|
||||||
|
String get editHistory;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1122,4 +1122,22 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get messageWasDeleted => 'Nachricht wurde gelöscht.';
|
String get messageWasDeleted => 'Nachricht wurde gelöscht.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sent => 'Versendet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sentTo => 'Zugestellt an';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get received => 'Empfangen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get opened => 'Geöffnet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingForInternet => 'Warten auf Internet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editHistory => 'Bearbeitungshistorie';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1115,4 +1115,22 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get messageWasDeleted => 'Message has been deleted.';
|
String get messageWasDeleted => 'Message has been deleted.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sent => 'Delivered';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get sentTo => 'Delivered to';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get received => 'Received';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get opened => 'Opened';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get waitingForInternet => 'Waiting for internet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get editHistory => 'Edit history';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -251,19 +251,22 @@ Future<void> notifyContactAboutOpeningMessage(
|
||||||
}
|
}
|
||||||
Log.info('Opened messages: $messageOtherIds');
|
Log.info('Opened messages: $messageOtherIds');
|
||||||
|
|
||||||
|
final actionAt = DateTime.now();
|
||||||
|
|
||||||
await sendCipherText(
|
await sendCipherText(
|
||||||
contactId,
|
contactId,
|
||||||
pb.EncryptedContent(
|
pb.EncryptedContent(
|
||||||
messageUpdate: pb.EncryptedContent_MessageUpdate(
|
messageUpdate: pb.EncryptedContent_MessageUpdate(
|
||||||
type: pb.EncryptedContent_MessageUpdate_Type.OPENED,
|
type: pb.EncryptedContent_MessageUpdate_Type.OPENED,
|
||||||
multipleTargetMessageIds: messageOtherIds,
|
multipleTargetMessageIds: messageOtherIds,
|
||||||
|
timestamp: Int64(actionAt.millisecondsSinceEpoch),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
for (final messageId in messageOtherIds) {
|
for (final messageId in messageOtherIds) {
|
||||||
await twonlyDB.messagesDao.updateMessageId(
|
await twonlyDB.messagesDao.updateMessageId(
|
||||||
messageId,
|
messageId,
|
||||||
MessagesCompanion(openedAt: Value(DateTime.now())),
|
MessagesCompanion(openedAt: Value(actionAt)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await updateLastMessageId(contactId, biggestMessageId);
|
await updateLastMessageId(contactId, biggestMessageId);
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ Future<void> handleTextMessage(
|
||||||
textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
|
textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
|
||||||
),
|
),
|
||||||
createdAt: Value(fromTimestamp(textMessage.timestamp)),
|
createdAt: Value(fromTimestamp(textMessage.timestamp)),
|
||||||
|
ackByServer: Value(DateTime.now()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
|
|
|
||||||
|
|
@ -348,3 +348,27 @@ String getUUIDforDirectChat(int a, int b) {
|
||||||
];
|
];
|
||||||
return parts.join('-');
|
return parts.join('-');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String friendlyDateTime(
|
||||||
|
BuildContext context,
|
||||||
|
DateTime dt, {
|
||||||
|
bool includeSeconds = false,
|
||||||
|
Locale? locale,
|
||||||
|
}) {
|
||||||
|
// Build date part
|
||||||
|
final datePart =
|
||||||
|
DateFormat.yMd(Localizations.localeOf(context).toString()).format(dt);
|
||||||
|
|
||||||
|
final use24Hour = MediaQuery.of(context).alwaysUse24HourFormat;
|
||||||
|
|
||||||
|
var timePart = '';
|
||||||
|
if (use24Hour) {
|
||||||
|
timePart =
|
||||||
|
DateFormat.jm(Localizations.localeOf(context).toString()).format(dt);
|
||||||
|
} else {
|
||||||
|
timePart =
|
||||||
|
DateFormat.Hm(Localizations.localeOf(context).toString()).format(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '$timePart $datePart';
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/chats/chat_messages_components/chat_list_entry.dart';
|
||||||
|
|
||||||
|
class MessageHistoryView extends StatelessWidget {
|
||||||
|
const MessageHistoryView({
|
||||||
|
required this.message,
|
||||||
|
required this.group,
|
||||||
|
required this.changes,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Message message;
|
||||||
|
final Group group;
|
||||||
|
final List<MessageHistory> changes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final json = message.toJson();
|
||||||
|
json['createdAt'] = message.modifiedAt;
|
||||||
|
final currentMessage = Message.fromJson(json);
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
height: 450,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(32),
|
||||||
|
topRight: Radius.circular(32),
|
||||||
|
),
|
||||||
|
color: context.color.surface,
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(
|
||||||
|
blurRadius: 10.9,
|
||||||
|
color: Color.fromRGBO(0, 0, 0, 0.1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.all(30),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
height: 3,
|
||||||
|
width: 60,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
ChatListEntry(
|
||||||
|
group: group,
|
||||||
|
message: currentMessage,
|
||||||
|
hideReactions: true,
|
||||||
|
),
|
||||||
|
...changes.map(
|
||||||
|
(change) {
|
||||||
|
final json = message.toJson();
|
||||||
|
json['content'] = change.content;
|
||||||
|
json['createdAt'] = change.createdAt;
|
||||||
|
final msgChanged = Message.fromJson(json);
|
||||||
|
return ChatListEntry(
|
||||||
|
group: group,
|
||||||
|
message: msgChanged,
|
||||||
|
hideReactions: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,23 +17,23 @@ import 'package:twonly/src/views/chats/chat_messages_components/response_contain
|
||||||
class ChatListEntry extends StatefulWidget {
|
class ChatListEntry extends StatefulWidget {
|
||||||
const ChatListEntry({
|
const ChatListEntry({
|
||||||
required this.group,
|
required this.group,
|
||||||
required this.galleryItems,
|
|
||||||
required this.prevMessage,
|
|
||||||
required this.message,
|
required this.message,
|
||||||
required this.nextMessage,
|
this.galleryItems = const [],
|
||||||
required this.onResponseTriggered,
|
this.scrollToMessage,
|
||||||
required this.scrollToMessage,
|
this.onResponseTriggered,
|
||||||
this.disableContextMenu = false,
|
this.prevMessage,
|
||||||
|
this.nextMessage,
|
||||||
|
this.hideReactions = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final Message? prevMessage;
|
final Message? prevMessage;
|
||||||
final Message? nextMessage;
|
final Message? nextMessage;
|
||||||
final Message message;
|
final Message message;
|
||||||
final Group group;
|
final Group group;
|
||||||
|
final bool hideReactions;
|
||||||
final List<MemoryItem> galleryItems;
|
final List<MemoryItem> galleryItems;
|
||||||
final void Function(String) scrollToMessage;
|
final void Function(String)? scrollToMessage;
|
||||||
final void Function() onResponseTriggered;
|
final void Function()? onResponseTriggered;
|
||||||
final bool disableContextMenu;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatListEntry> createState() => _ChatListEntryState();
|
State<ChatListEntry> createState() => _ChatListEntryState();
|
||||||
|
|
@ -98,77 +98,70 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
||||||
reactions.where((t) => seen.add(t.emoji)).toList().length;
|
reactions.where((t) => seen.add(t.emoji)).toList().length;
|
||||||
if (reactionsForWidth > 4) reactionsForWidth = 4;
|
if (reactionsForWidth > 4) reactionsForWidth = 4;
|
||||||
|
|
||||||
Widget child = Column(
|
Widget child = Stack(
|
||||||
mainAxisAlignment:
|
// overflow: Overflow.visible,
|
||||||
right ? MainAxisAlignment.end : MainAxisAlignment.start,
|
// clipBehavior: Clip.none,
|
||||||
crossAxisAlignment:
|
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||||
right ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
MessageActions(
|
if (widget.message.isDeletedFromSender)
|
||||||
message: widget.message,
|
ChatTextEntry(
|
||||||
onResponseTriggered: widget.onResponseTriggered,
|
message: widget.message,
|
||||||
child: Stack(
|
nextMessage: widget.nextMessage,
|
||||||
// overflow: Overflow.visible,
|
borderRadius: borderRadius,
|
||||||
// clipBehavior: Clip.none,
|
minWidth: reactionsForWidth * 43,
|
||||||
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
)
|
||||||
|
else
|
||||||
|
Column(
|
||||||
children: [
|
children: [
|
||||||
if (widget.message.isDeletedFromSender)
|
ResponseContainer(
|
||||||
ChatTextEntry(
|
msg: widget.message,
|
||||||
message: widget.message,
|
group: widget.group,
|
||||||
nextMessage: widget.nextMessage,
|
mediaService: mediaService,
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
minWidth: reactionsForWidth * 43,
|
scrollToMessage: widget.scrollToMessage,
|
||||||
)
|
child: (widget.message.type == MessageType.text)
|
||||||
else
|
? ChatTextEntry(
|
||||||
Column(
|
message: widget.message,
|
||||||
children: [
|
nextMessage: widget.nextMessage,
|
||||||
ResponseContainer(
|
borderRadius: borderRadius,
|
||||||
msg: widget.message,
|
minWidth: reactionsForWidth * 43,
|
||||||
group: widget.group,
|
)
|
||||||
mediaService: mediaService,
|
: (mediaService == null)
|
||||||
borderRadius: borderRadius,
|
? null
|
||||||
scrollToMessage: widget.scrollToMessage,
|
: ChatMediaEntry(
|
||||||
child: (widget.message.type == MessageType.text)
|
message: widget.message,
|
||||||
? ChatTextEntry(
|
group: widget.group,
|
||||||
message: widget.message,
|
mediaService: mediaService!,
|
||||||
nextMessage: widget.nextMessage,
|
galleryItems: widget.galleryItems,
|
||||||
borderRadius: borderRadius,
|
),
|
||||||
minWidth: reactionsForWidth * 43,
|
),
|
||||||
)
|
if (reactionsForWidth > 0) const SizedBox(height: 20, width: 10),
|
||||||
: (mediaService == null)
|
|
||||||
? null
|
|
||||||
: ChatMediaEntry(
|
|
||||||
message: widget.message,
|
|
||||||
group: widget.group,
|
|
||||||
mediaService: mediaService!,
|
|
||||||
galleryItems: widget.galleryItems,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (reactionsForWidth > 0)
|
|
||||||
const SizedBox(height: 20, width: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (!widget.message.isDeletedFromSender)
|
|
||||||
Positioned(
|
|
||||||
bottom: -20,
|
|
||||||
left: 5,
|
|
||||||
right: 5,
|
|
||||||
child: ReactionRow(
|
|
||||||
message: widget.message,
|
|
||||||
reactions: reactions,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
if (!widget.message.isDeletedFromSender && !widget.hideReactions)
|
||||||
|
Positioned(
|
||||||
|
bottom: -20,
|
||||||
|
left: 5,
|
||||||
|
right: 5,
|
||||||
|
child: ReactionRow(
|
||||||
|
message: widget.message,
|
||||||
|
reactions: reactions,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!widget.disableContextMenu) {
|
if (widget.onResponseTriggered != null) {
|
||||||
|
child = MessageActions(
|
||||||
|
message: widget.message,
|
||||||
|
onResponseTriggered: widget.onResponseTriggered!,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
child = MessageContextMenu(
|
child = MessageContextMenu(
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
group: widget.group,
|
group: widget.group,
|
||||||
onResponseTriggered: widget.onResponseTriggered,
|
onResponseTriggered: widget.onResponseTriggered!,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart';
|
import 'package:twonly/src/views/chats/chat_messages_components/bottom_sheets/all_reactions.bottom_sheet.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
|
|
||||||
|
|
@ -26,7 +27,6 @@ class ReactionRow extends StatelessWidget {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// if (layer == null) return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -99,7 +99,9 @@ class ReactionRow extends StatelessWidget {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(),
|
border: Border.all(),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
color: const Color.fromARGB(255, 74, 74, 74),
|
color: isDarkMode(context)
|
||||||
|
? const Color.fromARGB(255, 74, 74, 74)
|
||||||
|
: const Color.fromARGB(255, 197, 197, 197),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -107,8 +109,16 @@ class ReactionRow extends StatelessWidget {
|
||||||
if (entry.$2 > 1)
|
if (entry.$2 > 1)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 19,
|
height: 19,
|
||||||
|
width: 13,
|
||||||
child: Text(
|
child: Text(
|
||||||
entry.$2.toString(),
|
entry.$2.toString(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
color: Colors.black,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,6 @@ class ChatTextEntry extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var text = message.content ?? '';
|
var text = message.content ?? '';
|
||||||
|
|
||||||
if (message.isDeletedFromSender) {
|
|
||||||
text = context.lang.messageWasDeleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EmojiAnimation.supported(text)) {
|
if (EmojiAnimation.supported(text)) {
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
|
|
@ -61,6 +57,11 @@ class ChatTextEntry extends StatelessWidget {
|
||||||
expanded = true;
|
expanded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.isDeletedFromSender) {
|
||||||
|
text = context.lang.messageWasDeleted;
|
||||||
|
color = Colors.grey;
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
|
@ -109,6 +110,8 @@ class ChatTextEntry extends StatelessWidget {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Colors.white.withAlpha(150),
|
color: Colors.white.withAlpha(150),
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ class ResponseContainer extends StatefulWidget {
|
||||||
required this.msg,
|
required this.msg,
|
||||||
required this.group,
|
required this.group,
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.scrollToMessage,
|
|
||||||
required this.mediaService,
|
required this.mediaService,
|
||||||
required this.borderRadius,
|
required this.borderRadius,
|
||||||
|
this.scrollToMessage,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ class ResponseContainer extends StatefulWidget {
|
||||||
final Group group;
|
final Group group;
|
||||||
final MediaFileService? mediaService;
|
final MediaFileService? mediaService;
|
||||||
final BorderRadius borderRadius;
|
final BorderRadius borderRadius;
|
||||||
final void Function(String) scrollToMessage;
|
final void Function(String)? scrollToMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ResponseContainer> createState() => _ResponseContainerState();
|
State<ResponseContainer> createState() => _ResponseContainerState();
|
||||||
|
|
@ -65,7 +65,9 @@ class _ResponseContainerState extends State<ResponseContainer> {
|
||||||
return widget.child!;
|
return widget.child!;
|
||||||
}
|
}
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => widget.scrollToMessage(widget.msg.quotesMessageId!),
|
onTap: widget.scrollToMessage == null
|
||||||
|
? null
|
||||||
|
: () => widget.scrollToMessage!(widget.msg.quotesMessageId!),
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
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/messages.table.dart';
|
||||||
import 'package:twonly/src/database/twonly.db.dart';
|
import 'package:twonly/src/database/twonly.db.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/chats/chat_messages_components/bottom_sheets/message_history.bottom_sheet.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/components/avatar_icon.component.dart';
|
||||||
|
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||||
|
|
||||||
class MessageInfoView extends StatefulWidget {
|
class MessageInfoView extends StatefulWidget {
|
||||||
const MessageInfoView({
|
const MessageInfoView({
|
||||||
|
|
@ -18,22 +27,132 @@ class MessageInfoView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MessageInfoViewState extends State<MessageInfoView> {
|
class _MessageInfoViewState extends State<MessageInfoView> {
|
||||||
|
StreamSubscription<List<(MessageAction, Contact)>>? actionsStream;
|
||||||
|
StreamSubscription<List<MessageHistory>>? historyStream;
|
||||||
|
StreamSubscription<List<(GroupMember, Contact)>>? groupMemberStream;
|
||||||
|
|
||||||
|
List<(MessageAction, Contact)> messageActions = [];
|
||||||
|
List<MessageHistory> messageHistory = [];
|
||||||
|
List<(GroupMember, Contact)> groupMembers = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
initAsync();
|
initAsync();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
|
||||||
// watch message edit history
|
|
||||||
// watch message actions
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
actionsStream?.cancel();
|
||||||
|
historyStream?.cancel();
|
||||||
|
groupMemberStream?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initAsync() async {
|
||||||
|
final streamActions =
|
||||||
|
twonlyDB.messagesDao.watchMessageActions(widget.message.messageId);
|
||||||
|
actionsStream = streamActions.listen((update) {
|
||||||
|
setState(() {
|
||||||
|
messageActions = update;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
final streamGroup =
|
||||||
|
twonlyDB.messagesDao.watchMembersByGroupId(widget.message.groupId);
|
||||||
|
groupMemberStream = streamGroup.listen((update) {
|
||||||
|
setState(() {
|
||||||
|
groupMembers = update;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
final streamHistory =
|
||||||
|
twonlyDB.messagesDao.watchMessageHistory(widget.message.messageId);
|
||||||
|
historyStream = streamHistory.listen((update) {
|
||||||
|
setState(() {
|
||||||
|
messageHistory = update;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> getReceivedColumns(BuildContext context) {
|
||||||
|
if (widget.message.senderId != null) return [];
|
||||||
|
|
||||||
|
final columns = <Widget>[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(context.lang.sentTo),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (final groupMember in groupMembers) {
|
||||||
|
final ackByServer = messageActions.firstWhereOrNull(
|
||||||
|
(t) =>
|
||||||
|
t.$1.type == MessageActionType.ackByServerAt &&
|
||||||
|
t.$2.userId == groupMember.$2.userId,
|
||||||
|
);
|
||||||
|
final ackByUser = messageActions.firstWhereOrNull(
|
||||||
|
(t) =>
|
||||||
|
t.$1.type == MessageActionType.ackByUserAt &&
|
||||||
|
t.$2.userId == groupMember.$2.userId,
|
||||||
|
);
|
||||||
|
final openedByUser = messageActions.firstWhereOrNull(
|
||||||
|
(t) =>
|
||||||
|
t.$1.type == MessageActionType.openedAt &&
|
||||||
|
t.$2.userId == groupMember.$2.userId,
|
||||||
|
);
|
||||||
|
|
||||||
|
var actionTypeText = context.lang.waitingForInternet;
|
||||||
|
var actionAt = widget.message.createdAt;
|
||||||
|
if (ackByServer != null) {
|
||||||
|
actionTypeText = context.lang.sent;
|
||||||
|
actionAt = ackByServer.$1.actionAt;
|
||||||
|
}
|
||||||
|
if (ackByUser != null) {
|
||||||
|
actionTypeText = context.lang.received;
|
||||||
|
actionAt = ackByUser.$1.actionAt;
|
||||||
|
}
|
||||||
|
if (openedByUser != null) {
|
||||||
|
actionTypeText = context.lang.opened;
|
||||||
|
actionAt = openedByUser.$1.actionAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.add(
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
AvatarIcon(
|
||||||
|
contact: groupMember.$2,
|
||||||
|
fontSize: 15,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
getContactDisplayName(groupMember.$2),
|
||||||
|
style: const TextStyle(fontSize: 17),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
friendlyDateTime(context, actionAt),
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
Text(actionTypeText),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|
@ -41,40 +160,47 @@ class _MessageInfoViewState extends State<MessageInfoView> {
|
||||||
title: const Text(''),
|
title: const Text(''),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
const SizedBox(height: 20),
|
||||||
padding: const EdgeInsets.all(8),
|
ChatListEntry(
|
||||||
child: ChatListEntry(
|
group: widget.group,
|
||||||
group: widget.group,
|
message: widget.message,
|
||||||
galleryItems: const [],
|
),
|
||||||
prevMessage: null,
|
Text(
|
||||||
message: widget.message,
|
'${context.lang.sent}: ${friendlyDateTime(context, widget.message.createdAt)}',
|
||||||
disableContextMenu: true,
|
),
|
||||||
nextMessage: null,
|
if (widget.message.senderId != null &&
|
||||||
onResponseTriggered: () {},
|
widget.message.ackByServer != null)
|
||||||
scrollToMessage: (_) {},
|
Text(
|
||||||
|
'${context.lang.received}: ${friendlyDateTime(context, widget.message.ackByServer!)}',
|
||||||
),
|
),
|
||||||
),
|
if (messageHistory.isNotEmpty) ...[
|
||||||
Row(
|
const SizedBox(height: 10),
|
||||||
children: [
|
const Divider(),
|
||||||
const Text('Versendet'),
|
const SizedBox(height: 10),
|
||||||
const SizedBox(width: 13),
|
BetterListTile(
|
||||||
Text(formatDateTime(context, widget.message.createdAt)),
|
icon: FontAwesomeIcons.pencil,
|
||||||
],
|
padding: EdgeInsets.zero,
|
||||||
),
|
text: context.lang.editHistory,
|
||||||
// Row(
|
onTap: () async {
|
||||||
// children: [
|
// ignore: inference_failure_on_function_invocation
|
||||||
// Text("Empfangen"),
|
await showModalBottomSheet(
|
||||||
// SizedBox(width: 13),
|
context: context,
|
||||||
// Text(formatDateTime(context, widget.message.ackByUser)),
|
backgroundColor: Colors.black,
|
||||||
// ],
|
builder: (BuildContext context) {
|
||||||
// )
|
return MessageHistoryView(
|
||||||
const SizedBox(height: 10),
|
message: widget.message,
|
||||||
const Divider(),
|
changes: messageHistory,
|
||||||
const SizedBox(height: 10),
|
group: widget.group,
|
||||||
const Text('Zugestelt an'),
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
...getReceivedColumns(context),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ class BetterListTile extends StatelessWidget {
|
||||||
this.color,
|
this.color,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.iconSize = 20,
|
this.iconSize = 20,
|
||||||
|
this.padding,
|
||||||
});
|
});
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String text;
|
final String text;
|
||||||
|
|
@ -17,15 +18,18 @@ class BetterListTile extends StatelessWidget {
|
||||||
final Color? color;
|
final Color? color;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final double iconSize;
|
final double iconSize;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Padding(
|
leading: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: (padding == null)
|
||||||
right: 10,
|
? const EdgeInsets.only(
|
||||||
left: 19,
|
right: 10,
|
||||||
),
|
left: 19,
|
||||||
|
)
|
||||||
|
: padding!,
|
||||||
child: FaIcon(
|
child: FaIcon(
|
||||||
icon,
|
icon,
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,8 @@ class BetterText extends StatelessWidget {
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 17,
|
fontSize: 17,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue