mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +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();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return (select(messageActions)..where((t) => t.messageId.equals(messageId)))
|
||||
.watch();
|
||||
|
|
@ -410,6 +428,26 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
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 {
|
||||
// return (select(messages)
|
||||
// ..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.",
|
||||
"tabToRemoveEmoji": "Tippen um zu entfernen",
|
||||
"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.",
|
||||
"tabToRemoveEmoji": "Tab to remove",
|
||||
"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:
|
||||
/// **'Message has been deleted.'**
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1122,4 +1122,22 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
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
|
||||
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');
|
||||
|
||||
final actionAt = DateTime.now();
|
||||
|
||||
await sendCipherText(
|
||||
contactId,
|
||||
pb.EncryptedContent(
|
||||
messageUpdate: pb.EncryptedContent_MessageUpdate(
|
||||
type: pb.EncryptedContent_MessageUpdate_Type.OPENED,
|
||||
multipleTargetMessageIds: messageOtherIds,
|
||||
timestamp: Int64(actionAt.millisecondsSinceEpoch),
|
||||
),
|
||||
),
|
||||
);
|
||||
for (final messageId in messageOtherIds) {
|
||||
await twonlyDB.messagesDao.updateMessageId(
|
||||
messageId,
|
||||
MessagesCompanion(openedAt: Value(DateTime.now())),
|
||||
MessagesCompanion(openedAt: Value(actionAt)),
|
||||
);
|
||||
}
|
||||
await updateLastMessageId(contactId, biggestMessageId);
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ Future<void> handleTextMessage(
|
|||
textMessage.hasQuoteMessageId() ? textMessage.quoteMessageId : null,
|
||||
),
|
||||
createdAt: Value(fromTimestamp(textMessage.timestamp)),
|
||||
ackByServer: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
if (message != null) {
|
||||
|
|
|
|||
|
|
@ -348,3 +348,27 @@ String getUUIDforDirectChat(int a, int b) {
|
|||
];
|
||||
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 {
|
||||
const ChatListEntry({
|
||||
required this.group,
|
||||
required this.galleryItems,
|
||||
required this.prevMessage,
|
||||
required this.message,
|
||||
required this.nextMessage,
|
||||
required this.onResponseTriggered,
|
||||
required this.scrollToMessage,
|
||||
this.disableContextMenu = false,
|
||||
this.galleryItems = const [],
|
||||
this.scrollToMessage,
|
||||
this.onResponseTriggered,
|
||||
this.prevMessage,
|
||||
this.nextMessage,
|
||||
this.hideReactions = false,
|
||||
super.key,
|
||||
});
|
||||
final Message? prevMessage;
|
||||
final Message? nextMessage;
|
||||
final Message message;
|
||||
final Group group;
|
||||
final bool hideReactions;
|
||||
final List<MemoryItem> galleryItems;
|
||||
final void Function(String) scrollToMessage;
|
||||
final void Function() onResponseTriggered;
|
||||
final bool disableContextMenu;
|
||||
final void Function(String)? scrollToMessage;
|
||||
final void Function()? onResponseTriggered;
|
||||
|
||||
@override
|
||||
State<ChatListEntry> createState() => _ChatListEntryState();
|
||||
|
|
@ -98,16 +98,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
reactions.where((t) => seen.add(t.emoji)).toList().length;
|
||||
if (reactionsForWidth > 4) reactionsForWidth = 4;
|
||||
|
||||
Widget child = Column(
|
||||
mainAxisAlignment:
|
||||
right ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
right ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
MessageActions(
|
||||
message: widget.message,
|
||||
onResponseTriggered: widget.onResponseTriggered,
|
||||
child: Stack(
|
||||
Widget child = Stack(
|
||||
// overflow: Overflow.visible,
|
||||
// clipBehavior: Clip.none,
|
||||
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||
|
|
@ -144,11 +135,10 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
galleryItems: widget.galleryItems,
|
||||
),
|
||||
),
|
||||
if (reactionsForWidth > 0)
|
||||
const SizedBox(height: 20, width: 10),
|
||||
if (reactionsForWidth > 0) const SizedBox(height: 20, width: 10),
|
||||
],
|
||||
),
|
||||
if (!widget.message.isDeletedFromSender)
|
||||
if (!widget.message.isDeletedFromSender && !widget.hideReactions)
|
||||
Positioned(
|
||||
bottom: -20,
|
||||
left: 5,
|
||||
|
|
@ -159,16 +149,19 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
if (!widget.disableContextMenu) {
|
||||
if (widget.onResponseTriggered != null) {
|
||||
child = MessageActions(
|
||||
message: widget.message,
|
||||
onResponseTriggered: widget.onResponseTriggered!,
|
||||
child: child,
|
||||
);
|
||||
|
||||
child = MessageContextMenu(
|
||||
message: widget.message,
|
||||
group: widget.group,
|
||||
onResponseTriggered: widget.onResponseTriggered,
|
||||
onResponseTriggered: widget.onResponseTriggered!,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.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/components/animate_icon.dart';
|
||||
|
||||
|
|
@ -26,7 +27,6 @@ class ReactionRow extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
);
|
||||
// if (layer == null) return;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -99,7 +99,9 @@ class ReactionRow extends StatelessWidget {
|
|||
decoration: BoxDecoration(
|
||||
border: Border.all(),
|
||||
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(
|
||||
children: [
|
||||
|
|
@ -107,8 +109,16 @@ class ReactionRow extends StatelessWidget {
|
|||
if (entry.$2 > 1)
|
||||
SizedBox(
|
||||
height: 19,
|
||||
width: 13,
|
||||
child: Text(
|
||||
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) {
|
||||
var text = message.content ?? '';
|
||||
|
||||
if (message.isDeletedFromSender) {
|
||||
text = context.lang.messageWasDeleted;
|
||||
}
|
||||
|
||||
if (EmojiAnimation.supported(text)) {
|
||||
return Container(
|
||||
constraints: const BoxConstraints(
|
||||
|
|
@ -61,6 +57,11 @@ class ChatTextEntry extends StatelessWidget {
|
|||
expanded = true;
|
||||
}
|
||||
|
||||
if (message.isDeletedFromSender) {
|
||||
text = context.lang.messageWasDeleted;
|
||||
color = Colors.grey;
|
||||
}
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||
|
|
@ -109,6 +110,8 @@ class ChatTextEntry extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white.withAlpha(150),
|
||||
decoration: TextDecoration.none,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ class ResponseContainer extends StatefulWidget {
|
|||
required this.msg,
|
||||
required this.group,
|
||||
required this.child,
|
||||
required this.scrollToMessage,
|
||||
required this.mediaService,
|
||||
required this.borderRadius,
|
||||
this.scrollToMessage,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ class ResponseContainer extends StatefulWidget {
|
|||
final Group group;
|
||||
final MediaFileService? mediaService;
|
||||
final BorderRadius borderRadius;
|
||||
final void Function(String) scrollToMessage;
|
||||
final void Function(String)? scrollToMessage;
|
||||
|
||||
@override
|
||||
State<ResponseContainer> createState() => _ResponseContainerState();
|
||||
|
|
@ -65,7 +65,9 @@ class _ResponseContainerState extends State<ResponseContainer> {
|
|||
return widget.child!;
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () => widget.scrollToMessage(widget.msg.quotesMessageId!),
|
||||
onTap: widget.scrollToMessage == null
|
||||
? null
|
||||
: () => widget.scrollToMessage!(widget.msg.quotesMessageId!),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
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: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/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/components/avatar_icon.component.dart';
|
||||
import 'package:twonly/src/views/components/better_list_title.dart';
|
||||
|
||||
class MessageInfoView extends StatefulWidget {
|
||||
const MessageInfoView({
|
||||
|
|
@ -18,22 +27,132 @@ class MessageInfoView extends StatefulWidget {
|
|||
}
|
||||
|
||||
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
|
||||
void initState() {
|
||||
initAsync();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
// watch message edit history
|
||||
// watch message actions
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
actionsStream?.cancel();
|
||||
historyStream?.cancel();
|
||||
groupMemberStream?.cancel();
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
|
@ -41,40 +160,47 @@ class _MessageInfoViewState extends State<MessageInfoView> {
|
|||
title: const Text(''),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ChatListEntry(
|
||||
const SizedBox(height: 20),
|
||||
ChatListEntry(
|
||||
group: widget.group,
|
||||
galleryItems: const [],
|
||||
prevMessage: null,
|
||||
message: widget.message,
|
||||
disableContextMenu: true,
|
||||
nextMessage: null,
|
||||
onResponseTriggered: () {},
|
||||
scrollToMessage: (_) {},
|
||||
),
|
||||
Text(
|
||||
'${context.lang.sent}: ${friendlyDateTime(context, widget.message.createdAt)}',
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Text('Versendet'),
|
||||
const SizedBox(width: 13),
|
||||
Text(formatDateTime(context, widget.message.createdAt)),
|
||||
],
|
||||
if (widget.message.senderId != null &&
|
||||
widget.message.ackByServer != null)
|
||||
Text(
|
||||
'${context.lang.received}: ${friendlyDateTime(context, widget.message.ackByServer!)}',
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// Text("Empfangen"),
|
||||
// SizedBox(width: 13),
|
||||
// Text(formatDateTime(context, widget.message.ackByUser)),
|
||||
// ],
|
||||
// )
|
||||
if (messageHistory.isNotEmpty) ...[
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
const Text('Zugestelt an'),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.pencil,
|
||||
padding: EdgeInsets.zero,
|
||||
text: context.lang.editHistory,
|
||||
onTap: () async {
|
||||
// ignore: inference_failure_on_function_invocation
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.black,
|
||||
builder: (BuildContext context) {
|
||||
return MessageHistoryView(
|
||||
message: widget.message,
|
||||
changes: messageHistory,
|
||||
group: widget.group,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
...getReceivedColumns(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ class BetterListTile extends StatelessWidget {
|
|||
this.color,
|
||||
this.subtitle,
|
||||
this.iconSize = 20,
|
||||
this.padding,
|
||||
});
|
||||
final IconData icon;
|
||||
final String text;
|
||||
|
|
@ -17,15 +18,18 @@ class BetterListTile extends StatelessWidget {
|
|||
final Color? color;
|
||||
final VoidCallback onTap;
|
||||
final double iconSize;
|
||||
final EdgeInsets? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
padding: (padding == null)
|
||||
? const EdgeInsets.only(
|
||||
right: 10,
|
||||
left: 19,
|
||||
),
|
||||
)
|
||||
: padding!,
|
||||
child: FaIcon(
|
||||
icon,
|
||||
size: iconSize,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ class BetterText extends StatelessWidget {
|
|||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17,
|
||||
decoration: TextDecoration.none,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue