swith to event aggregations and render message edits and reactions

This commit is contained in:
Sorunome 2020-08-12 09:30:31 +00:00 committed by Christian Pauly
commit 6fa9c182f2
11 changed files with 512 additions and 150 deletions

View file

@ -12,6 +12,7 @@ import 'package:flutter/material.dart';
import '../adaptive_page_layout.dart';
import '../avatar.dart';
import '../matrix.dart';
import '../message_reactions.dart';
import 'state_message.dart';
class Message extends StatelessWidget {
@ -64,11 +65,13 @@ class Message extends StatelessWidget {
var rowMainAxisAlignment =
ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start;
final displayEvent = event.getDisplayEvent(timeline);
if (event.showThumbnail) {
color = Theme.of(context).scaffoldBackgroundColor.withOpacity(0.66);
textColor = Theme.of(context).textTheme.bodyText2.color;
} else if (ownMessage) {
color = event.status == -1
color = displayEvent.status == -1
? Colors.redAccent
: Theme.of(context).primaryColor;
}
@ -91,15 +94,14 @@ class Message extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (event.isReply)
if (event.relationshipType == RelationshipTypes.Reply)
FutureBuilder<Event>(
future: event.getReplyEvent(timeline),
builder: (BuildContext context, snapshot) {
final replyEvent = snapshot.hasData
? snapshot.data
: Event(
eventId: event.content['m.relates_to']
['m.in_reply_to']['event_id'],
eventId: event.relationshipEventId,
content: {'msgtype': 'm.text', 'body': '...'},
senderId: event.senderId,
type: 'm.room.message',
@ -110,18 +112,18 @@ class Message extends StatelessWidget {
);
return Container(
margin: EdgeInsets.symmetric(vertical: 4.0),
child:
ReplyContent(replyEvent, lightText: ownMessage),
child: ReplyContent(replyEvent,
lightText: ownMessage, timeline: timeline),
);
},
),
MessageContent(
event,
displayEvent,
textColor: textColor,
),
if (event.type == EventTypes.Encrypted &&
event.messageType == MessageTypes.BadEncrypted &&
event.content['can_request_session'] == true)
if (displayEvent.type == EventTypes.Encrypted &&
displayEvent.messageType == MessageTypes.BadEncrypted &&
displayEvent.content['can_request_session'] == true)
RaisedButton(
color: color.withAlpha(100),
child: Text(
@ -129,15 +131,18 @@ class Message extends StatelessWidget {
style: TextStyle(color: textColor),
),
onPressed: () => SimpleDialogs(context)
.tryRequestWithLoadingDialog(event.requestKey()),
.tryRequestWithLoadingDialog(
displayEvent.requestKey()),
),
SizedBox(height: 4),
Opacity(
opacity: 0,
child: _MetaRow(
event,
event, // meta information should be from the unedited event
ownMessage,
textColor,
timeline,
displayEvent,
),
),
],
@ -150,6 +155,8 @@ class Message extends StatelessWidget {
event,
ownMessage,
textColor,
timeline,
displayEvent,
),
),
],
@ -170,6 +177,32 @@ class Message extends StatelessWidget {
} else {
rowChildren.insert(0, avatarOrSizedBox);
}
final row = Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: rowMainAxisAlignment,
children: rowChildren,
);
Widget container;
if (event.hasAggregatedEvents(timeline, RelationshipTypes.Reaction)) {
container = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
row,
Padding(
padding: EdgeInsets.only(
top: 4.0,
left: (ownMessage ? 0 : Avatar.defaultSize) + 12.0,
right: (ownMessage ? Avatar.defaultSize : 0) + 12.0,
),
child: MessageReactions(event, timeline),
),
],
);
} else {
container = row;
}
return InkWell(
onHover: (b) => useMouse = true,
@ -185,11 +218,7 @@ class Message extends StatelessWidget {
child: Padding(
padding: EdgeInsets.only(
left: 8.0, right: 8.0, bottom: sameSender ? 4.0 : 8.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: rowMainAxisAlignment,
children: rowChildren,
),
child: container,
),
),
);
@ -200,8 +229,12 @@ class _MetaRow extends StatelessWidget {
final Event event;
final bool ownMessage;
final Color color;
final Timeline timeline;
final Event displayEvent;
const _MetaRow(this.event, this.ownMessage, this.color, {Key key})
const _MetaRow(
this.event, this.ownMessage, this.color, this.timeline, this.displayEvent,
{Key key})
: super(key: key);
@override
@ -229,10 +262,16 @@ class _MetaRow extends StatelessWidget {
fontSize: 11,
),
),
if (event.hasAggregatedEvents(timeline, RelationshipTypes.Edit))
Icon(
Icons.edit,
size: 12,
color: color,
),
if (ownMessage) SizedBox(width: 2),
if (ownMessage)
Icon(
event.statusIcon,
displayEvent.statusIcon,
size: 12,
color: color,
),

View file

@ -194,7 +194,6 @@ class MatrixState extends State<Matrix> {
verificationMethods.add(KeyVerificationMethod.emoji);
}
client = Client(widget.clientName,
debug: false,
enableE2eeRecovery: true,
verificationMethods: verificationMethods,
importantStateEvents: <String>{

View file

@ -40,7 +40,6 @@ class MessageContent extends StatelessWidget {
case MessageTypes.Text:
case MessageTypes.Notice:
case MessageTypes.Emote:
case MessageTypes.Reply:
if (Matrix.of(context).renderHtml &&
!event.redacted &&
event.content['format'] == 'org.matrix.custom.html' &&

View file

@ -0,0 +1,140 @@
import 'package:famedlysdk/famedlysdk.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_networkimage/provider.dart';
import 'matrix.dart';
class MessageReactions extends StatelessWidget {
final Event event;
final Timeline timeline;
const MessageReactions(this.event, this.timeline);
@override
Widget build(BuildContext context) {
final allReactionEvents =
event.aggregatedEvents(timeline, RelationshipTypes.Reaction);
final reactionMap = <String, _ReactionEntry>{};
for (final e in allReactionEvents) {
if (e.content['m.relates_to'].containsKey('key')) {
final key = e.content['m.relates_to']['key'];
if (!reactionMap.containsKey(key)) {
reactionMap[key] = _ReactionEntry(
key: key,
count: 0,
reacted: false,
);
}
reactionMap[key].count++;
reactionMap[key].reacted |= e.senderId == e.room.client.userID;
}
}
final reactionList = reactionMap.values.toList();
reactionList.sort((a, b) => b.count - a.count > 0 ? 1 : -1);
return Wrap(
spacing: 4.0,
runSpacing: 4.0,
children: reactionList
.map((r) => _Reaction(
reactionKey: r.key,
count: r.count,
reacted: r.reacted,
onTap: () {
if (r.reacted) {
final evt = allReactionEvents.firstWhere(
(e) =>
e.senderId == e.room.client.userID &&
e.content['m.relates_to']['key'] == r.key,
orElse: () => null);
if (evt != null) {
evt.redact();
}
} else {
event.room.sendReaction(event.eventId, r.key);
}
},
))
.toList(),
);
}
}
class _Reaction extends StatelessWidget {
final String reactionKey;
final int count;
final bool reacted;
final void Function() onTap;
const _Reaction({this.reactionKey, this.count, this.reacted, this.onTap});
@override
Widget build(BuildContext context) {
final borderColor = reacted ? Colors.red : Theme.of(context).primaryColor;
final textColor = Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black;
final color = Theme.of(context).secondaryHeaderColor;
final fontSize = DefaultTextStyle.of(context).style.fontSize;
final padding = fontSize / 5;
Widget content;
if (reactionKey.startsWith('mxc://')) {
final src = Uri.parse(reactionKey)?.getThumbnail(
Matrix.of(context).client,
width: 9999,
height: fontSize * MediaQuery.of(context).devicePixelRatio,
method: ThumbnailMethod.scale,
);
content = Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image(
image: AdvancedNetworkImage(
src,
useDiskCache: !kIsWeb,
),
height: fontSize,
),
Text(count.toString(),
style: TextStyle(
color: textColor,
fontSize: DefaultTextStyle.of(context).style.fontSize,
)),
],
);
} else {
var renderKey = reactionKey;
if (renderKey.length > 10) {
renderKey = renderKey.substring(0, 7) + '...';
}
content = Text('$renderKey $count',
style: TextStyle(
color: textColor,
fontSize: DefaultTextStyle.of(context).style.fontSize,
));
}
return InkWell(
child: Container(
decoration: BoxDecoration(
color: color,
border: Border.all(
width: fontSize / 20,
color: borderColor,
),
borderRadius: BorderRadius.all(Radius.circular(padding * 2)),
),
padding: EdgeInsets.all(padding),
child: content,
),
onTap: () => onTap != null ? onTap() : null,
);
}
}
class _ReactionEntry {
String key;
int count;
bool reacted;
_ReactionEntry({this.key, this.count, this.reacted});
}

View file

@ -8,23 +8,29 @@ import 'matrix.dart';
class ReplyContent extends StatelessWidget {
final Event replyEvent;
final bool lightText;
final Timeline timeline;
const ReplyContent(this.replyEvent, {this.lightText = false, Key key})
const ReplyContent(this.replyEvent,
{this.lightText = false, Key key, this.timeline})
: super(key: key);
@override
Widget build(BuildContext context) {
Widget replyBody;
if (replyEvent != null &&
final displayEvent = replyEvent != null && timeline != null
? replyEvent.getDisplayEvent(timeline)
: replyEvent;
if (displayEvent != null &&
Matrix.of(context).renderHtml &&
[EventTypes.Message, EventTypes.Encrypted].contains(replyEvent.type) &&
[EventTypes.Message, EventTypes.Encrypted]
.contains(displayEvent.type) &&
[MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote]
.contains(replyEvent.messageType) &&
!replyEvent.redacted &&
replyEvent.content['format'] == 'org.matrix.custom.html' &&
replyEvent.content['formatted_body'] is String) {
String html = replyEvent.content['formatted_body'];
if (replyEvent.messageType == MessageTypes.Emote) {
.contains(displayEvent.messageType) &&
!displayEvent.redacted &&
displayEvent.content['format'] == 'org.matrix.custom.html' &&
displayEvent.content['formatted_body'] is String) {
String html = displayEvent.content['formatted_body'];
if (displayEvent.messageType == MessageTypes.Emote) {
html = '* $html';
}
replyBody = HtmlMessage(
@ -36,11 +42,11 @@ class ReplyContent extends StatelessWidget {
fontSize: DefaultTextStyle.of(context).style.fontSize,
),
maxLines: 1,
room: replyEvent.room,
room: displayEvent.room,
);
} else {
replyBody = Text(
replyEvent?.getLocalizedBody(
displayEvent?.getLocalizedBody(
L10n.of(context),
withSenderNamePrefix: false,
hideReply: true,
@ -71,7 +77,7 @@ class ReplyContent extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
(replyEvent?.sender?.calcDisplayname() ?? '') + ':',
(displayEvent?.sender?.calcDisplayname() ?? '') + ':',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(