mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 11:18:41 +00:00
starting with message info page
This commit is contained in:
parent
8f8f2cabe0
commit
4f68d22e07
10 changed files with 342 additions and 111 deletions
|
|
@ -191,13 +191,13 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
}
|
||||
|
||||
Future<void> handleTextEdit(
|
||||
int contactId,
|
||||
int? contactId,
|
||||
String messageId,
|
||||
String text,
|
||||
DateTime timestamp,
|
||||
) async {
|
||||
final msg = await getMessageById(messageId).getSingleOrNull();
|
||||
if (msg == null || msg.content == null || msg.senderId == contactId) {
|
||||
if (msg == null || msg.content == null || msg.senderId != contactId) {
|
||||
return;
|
||||
}
|
||||
await into(messageHistories).insert(
|
||||
|
|
@ -209,11 +209,12 @@ class MessagesDao extends DatabaseAccessor<TwonlyDB> with _$MessagesDaoMixin {
|
|||
);
|
||||
await (update(messages)
|
||||
..where(
|
||||
(t) => t.messageId.equals(messageId) & t.senderId.equals(contactId),
|
||||
(t) => t.messageId.equals(messageId),
|
||||
))
|
||||
.write(
|
||||
MessagesCompanion(
|
||||
content: Value(text),
|
||||
modifiedAt: Value(timestamp),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@
|
|||
"submit": "Abschicken",
|
||||
"close": "Schließen",
|
||||
"cancel": "Abbrechen",
|
||||
"edit": "Bearbeiten",
|
||||
"ok": "Ok",
|
||||
"now": "Jetzt",
|
||||
"you": "Du",
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@
|
|||
"react": "React",
|
||||
"reply": "Reply",
|
||||
"copy": "Copy",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"info": "Info",
|
||||
"ok": "Ok",
|
||||
|
|
|
|||
|
|
@ -1106,6 +1106,12 @@ abstract class AppLocalizations {
|
|||
/// **'Copy'**
|
||||
String get copy;
|
||||
|
||||
/// No description provided for @edit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit'**
|
||||
String get edit;
|
||||
|
||||
/// No description provided for @delete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -560,6 +560,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get copy => 'Kopieren';
|
||||
|
||||
@override
|
||||
String get edit => 'Bearbeiten';
|
||||
|
||||
@override
|
||||
String get delete => 'Löschen';
|
||||
|
||||
|
|
|
|||
|
|
@ -555,6 +555,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get copy => 'Copy';
|
||||
|
||||
@override
|
||||
String get edit => 'Edit';
|
||||
|
||||
@override
|
||||
String get delete => 'Delete';
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class ChatListEntry extends StatefulWidget {
|
|||
required this.nextMessage,
|
||||
required this.onResponseTriggered,
|
||||
required this.scrollToMessage,
|
||||
this.disableContextMenu = false,
|
||||
super.key,
|
||||
});
|
||||
final Message? prevMessage;
|
||||
|
|
@ -32,6 +33,7 @@ class ChatListEntry extends StatefulWidget {
|
|||
final List<MemoryItem> galleryItems;
|
||||
final void Function(String) scrollToMessage;
|
||||
final void Function() onResponseTriggered;
|
||||
final bool disableContextMenu;
|
||||
|
||||
@override
|
||||
State<ChatListEntry> createState() => _ChatListEntryState();
|
||||
|
|
@ -96,14 +98,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
reactions.where((t) => seen.add(t.emoji)).toList().length;
|
||||
if (reactionsForWidth > 4) reactionsForWidth = 4;
|
||||
|
||||
return Align(
|
||||
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: MessageContextMenu(
|
||||
message: widget.message,
|
||||
onResponseTriggered: widget.onResponseTriggered,
|
||||
child: Column(
|
||||
Widget child = Column(
|
||||
mainAxisAlignment:
|
||||
right ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
|
|
@ -115,8 +110,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
child: Stack(
|
||||
// overflow: Overflow.visible,
|
||||
// clipBehavior: Clip.none,
|
||||
alignment:
|
||||
right ? Alignment.centerRight : Alignment.centerLeft,
|
||||
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||
children: [
|
||||
if (widget.message.isDeletedFromSender)
|
||||
ChatTextEntry(
|
||||
|
|
@ -168,9 +162,20 @@ class _ChatListEntryState extends State<ChatListEntry> {
|
|||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (!widget.disableContextMenu) {
|
||||
child = MessageContextMenu(
|
||||
message: widget.message,
|
||||
group: widget.group,
|
||||
onResponseTriggered: widget.onResponseTriggered,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return Align(
|
||||
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: Padding(padding: padding, child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:intl/intl.dart' hide TextDirection;
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
|
|
@ -89,13 +90,29 @@ class ChatTextEntry extends StatelessWidget {
|
|||
alignment: AlignmentGeometry.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
child: Text(
|
||||
child: Row(
|
||||
children: [
|
||||
if (message.modifiedAt != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: SizedBox(
|
||||
height: 10,
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.pencil,
|
||||
color: Colors.white.withAlpha(150),
|
||||
size: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
friendlyTime(context, message.createdAt),
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white.withAlpha(150),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
// ignore_for_file: inference_failure_on_function_invocation
|
||||
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/tables/messages.table.dart';
|
||||
import 'package:twonly/src/database/twonly.db.dart';
|
||||
import 'package:twonly/src/model/protobuf/client/generated/messages.pbserver.dart'
|
||||
as pb;
|
||||
|
|
@ -12,15 +14,18 @@ import 'package:twonly/src/services/api/messages.dart';
|
|||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
||||
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
|
||||
import 'package:twonly/src/views/chats/message_info.view.dart';
|
||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||
|
||||
class MessageContextMenu extends StatelessWidget {
|
||||
const MessageContextMenu({
|
||||
required this.message,
|
||||
required this.group,
|
||||
required this.child,
|
||||
required this.onResponseTriggered,
|
||||
super.key,
|
||||
});
|
||||
final Group group;
|
||||
final Widget child;
|
||||
final Message message;
|
||||
final VoidCallback onResponseTriggered;
|
||||
|
|
@ -35,6 +40,7 @@ class MessageContextMenu extends StatelessWidget {
|
|||
}
|
||||
},
|
||||
actions: [
|
||||
if (!message.isDeletedFromSender)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.react),
|
||||
onSelect: () async {
|
||||
|
|
@ -64,11 +70,22 @@ class MessageContextMenu extends StatelessWidget {
|
|||
},
|
||||
child: const FaIcon(FontAwesomeIcons.faceLaugh),
|
||||
),
|
||||
if (!message.isDeletedFromSender)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.reply),
|
||||
onSelect: onResponseTriggered,
|
||||
child: const FaIcon(FontAwesomeIcons.reply),
|
||||
),
|
||||
if (!message.isDeletedFromSender &&
|
||||
message.senderId == null &&
|
||||
message.type == MessageType.text)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.edit),
|
||||
onSelect: () async {
|
||||
await editTextMessage(context, message);
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.pencil),
|
||||
),
|
||||
if (message.content != null)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.copy),
|
||||
|
|
@ -115,13 +132,107 @@ class MessageContextMenu extends StatelessWidget {
|
|||
},
|
||||
child: const FaIcon(FontAwesomeIcons.trash),
|
||||
),
|
||||
// PieAction(
|
||||
// tooltip: Text(context.lang.info),
|
||||
// onSelect: () {},
|
||||
// child: const FaIcon(FontAwesomeIcons.circleInfo),
|
||||
// ),
|
||||
if (!message.isDeletedFromSender)
|
||||
PieAction(
|
||||
tooltip: Text(context.lang.info),
|
||||
onSelect: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return MessageInfoView(
|
||||
message: message,
|
||||
group: group,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.circleInfo),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> editTextMessage(BuildContext context, Message message) async {
|
||||
var newText = message.content;
|
||||
final controller = TextEditingController(text: message.content);
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
content: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: 4,
|
||||
minLines: 1,
|
||||
onChanged: (value) => setState(() {
|
||||
newText = value;
|
||||
}),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
textCapitalization: TextCapitalization.characters,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(context.lang.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (newText != null &&
|
||||
newText != message.content &&
|
||||
newText != '') {
|
||||
final timestamp = DateTime.now();
|
||||
|
||||
await twonlyDB.messagesDao.handleTextEdit(
|
||||
null,
|
||||
message.messageId,
|
||||
newText!,
|
||||
timestamp,
|
||||
);
|
||||
await sendCipherTextToGroup(
|
||||
message.groupId,
|
||||
pb.EncryptedContent(
|
||||
messageUpdate: pb.EncryptedContent_MessageUpdate(
|
||||
type: pb.EncryptedContent_MessageUpdate_Type.EDIT_TEXT,
|
||||
senderMessageId: message.messageId,
|
||||
text: newText,
|
||||
timestamp: Int64(
|
||||
timestamp.millisecondsSinceEpoch,
|
||||
),
|
||||
),
|
||||
),
|
||||
null,
|
||||
);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(context.lang.ok),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
83
lib/src/views/chats/message_info.view.dart
Normal file
83
lib/src/views/chats/message_info.view.dart
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
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 MessageInfoView extends StatefulWidget {
|
||||
const MessageInfoView({
|
||||
required this.message,
|
||||
required this.group,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Message message;
|
||||
final Group group;
|
||||
|
||||
@override
|
||||
State<MessageInfoView> createState() => _MessageInfoViewState();
|
||||
}
|
||||
|
||||
class _MessageInfoViewState extends State<MessageInfoView> {
|
||||
@override
|
||||
void initState() {
|
||||
initAsync();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
// watch message edit history
|
||||
// watch message actions
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(''),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ChatListEntry(
|
||||
group: widget.group,
|
||||
galleryItems: const [],
|
||||
prevMessage: null,
|
||||
message: widget.message,
|
||||
disableContextMenu: true,
|
||||
nextMessage: null,
|
||||
onResponseTriggered: () {},
|
||||
scrollToMessage: (_) {},
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Text('Versendet'),
|
||||
const SizedBox(width: 13),
|
||||
Text(formatDateTime(context, widget.message.createdAt)),
|
||||
],
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// Text("Empfangen"),
|
||||
// SizedBox(width: 13),
|
||||
// Text(formatDateTime(context, widget.message.ackByUser)),
|
||||
// ],
|
||||
// )
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
const Text('Zugestelt an'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue