feat: new design

This commit is contained in:
Krille Fear 2021-11-13 13:06:36 +01:00
commit 22fdb667ba
16 changed files with 625 additions and 512 deletions

View file

@ -4,7 +4,6 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions.dart/event_extension.dart';
import 'package:fluffychat/utils/string_color.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -20,6 +19,7 @@ class Message extends StatelessWidget {
final Event nextEvent;
final void Function(Event) onSelect;
final void Function(Event) onAvatarTab;
final void Function(Event) onInfoTab;
final void Function(String) scrollToEventId;
final void Function(String) unfold;
final bool longPressSelect;
@ -30,6 +30,7 @@ class Message extends StatelessWidget {
{this.nextEvent,
this.longPressSelect,
this.onSelect,
this.onInfoTab,
this.onAvatarTab,
this.scrollToEventId,
@required this.unfold,
@ -57,12 +58,19 @@ class Message extends StatelessWidget {
final client = Matrix.of(context).client;
final ownMessage = event.senderId == client.userID;
final alignment = ownMessage ? Alignment.topRight : Alignment.topLeft;
var color = Theme.of(context).secondaryHeaderColor;
var color = Theme.of(context).scaffoldBackgroundColor;
final displayTime = event.type == EventTypes.RoomCreate ||
nextEvent == null ||
!event.originServerTs.sameEnvironment(nextEvent.originServerTs);
final sameSender = nextEvent != null &&
[EventTypes.Message, EventTypes.Sticker].contains(nextEvent.type)
? nextEvent.sender.id == event.sender.id
[
EventTypes.Message,
EventTypes.Sticker,
EventTypes.Encrypted,
].contains(nextEvent.type)
? nextEvent.sender.id == event.sender.id && !displayTime
: false;
var textColor = ownMessage
final textColor = ownMessage
? Colors.white
: Theme.of(context).brightness == Brightness.dark
? Colors.white
@ -71,148 +79,182 @@ class Message extends StatelessWidget {
ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start;
final displayEvent = event.getDisplayEvent(timeline);
final borderRadius = BorderRadius.only(
topLeft: !ownMessage
? const Radius.circular(2)
: const Radius.circular(AppConfig.borderRadius),
topRight: ownMessage
? const Radius.circular(2)
: const Radius.circular(AppConfig.borderRadius),
bottomLeft: const Radius.circular(AppConfig.borderRadius),
bottomRight: const Radius.circular(AppConfig.borderRadius),
);
if (event.showThumbnail) {
color = Colors.transparent;
textColor = Theme.of(context).textTheme.bodyText2.color;
} else if (ownMessage) {
if (ownMessage) {
color = displayEvent.status.isError
? Colors.redAccent
: Theme.of(context).primaryColor;
}
final rowChildren = <Widget>[
sameSender || ownMessage
? SizedBox(
width: Avatar.defaultSize,
height: Avatar.defaultSize,
child: event.status == EventStatus.sending
? const Center(
child: SizedBox(
width: 16,
height: 16,
child:
CircularProgressIndicator.adaptive(strokeWidth: 2),
),
)
: null,
)
: Avatar(
event.sender.avatarUrl,
event.sender.calcDisplayname(),
onTap: () => onAvatarTab(event),
),
Expanded(
child: Container(
alignment: alignment,
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Material(
color: color,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: InkWell(
onHover: (b) => useMouse = true,
onTap: !useMouse && longPressSelect
? () => null
: () => onSelect(event),
onLongPress: !longPressSelect ? null : () => onSelect(event),
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
),
padding:
const EdgeInsets.symmetric(vertical: 6, horizontal: 10),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5),
child: Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (event.relationshipType == RelationshipTypes.reply)
FutureBuilder<Event>(
future: event.getReplyEvent(timeline),
builder: (BuildContext context, snapshot) {
final replyEvent = snapshot.hasData
? snapshot.data
: Event(
eventId: event.relationshipEventId,
content: {
'msgtype': 'm.text',
'body': '...'
},
senderId: event.senderId,
type: 'm.room.message',
room: event.room,
status: EventStatus.sent,
originServerTs: DateTime.now(),
);
return InkWell(
onTap: () {
if (scrollToEventId != null) {
scrollToEventId(replyEvent.eventId);
}
},
child: AbsorbPointer(
child: Container(
margin: const EdgeInsets.symmetric(
vertical: 4.0),
child: ReplyContent(replyEvent,
lightText: ownMessage,
timeline: timeline),
),
),
);
},
),
MessageContent(
displayEvent,
textColor: textColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (!ownMessage && !sameSender)
Padding(
padding: const EdgeInsets.only(left: 8.0, bottom: 4),
child: event.room.isDirectChat
? const SizedBox(height: 12)
: Text(
event.sender.calcDisplayname(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: event.sender.calcDisplayname().color,
),
const SizedBox(height: 3),
Opacity(
opacity: 0,
child: _MetaRow(
event, // meta information should be from the unedited event
ownMessage,
textColor,
timeline,
displayEvent,
),
),
),
Container(
alignment: alignment,
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Material(
color: color,
elevation: 6,
shadowColor:
Theme.of(context).secondaryHeaderColor.withAlpha(100),
borderRadius: borderRadius,
child: InkWell(
onHover: (b) => useMouse = true,
onTap: !useMouse && longPressSelect
? () => null
: () => onSelect(event),
onLongPress: !longPressSelect ? null : () => onSelect(event),
borderRadius: borderRadius,
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
),
padding: const EdgeInsets.all(16),
constraints: const BoxConstraints(
maxWidth: FluffyThemes.columnWidth * 1.5),
child: Stack(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (event.relationshipType ==
RelationshipTypes.reply)
FutureBuilder<Event>(
future: event.getReplyEvent(timeline),
builder: (BuildContext context, snapshot) {
final replyEvent = snapshot.hasData
? snapshot.data
: Event(
eventId: event.relationshipEventId,
content: {
'msgtype': 'm.text',
'body': '...'
},
senderId: event.senderId,
type: 'm.room.message',
room: event.room,
status: EventStatus.sent,
originServerTs: DateTime.now(),
);
return InkWell(
onTap: () {
if (scrollToEventId != null) {
scrollToEventId(replyEvent.eventId);
}
},
child: AbsorbPointer(
child: Container(
margin: const EdgeInsets.symmetric(
vertical: 4.0),
child: ReplyContent(replyEvent,
lightText: ownMessage,
timeline: timeline),
),
),
);
},
),
MessageContent(
displayEvent,
textColor: textColor,
onInfoTab: onInfoTab,
),
],
),
],
),
Positioned(
bottom: 0,
right: ownMessage ? 0 : null,
left: !ownMessage ? 0 : null,
child: _MetaRow(
event,
ownMessage,
textColor,
timeline,
displayEvent,
),
),
],
),
),
),
),
),
],
),
),
];
final avatarOrSizedBox = sameSender
? const SizedBox(width: Avatar.defaultSize)
: Avatar(
event.sender.avatarUrl,
event.sender.calcDisplayname(),
onTap: () => onAvatarTab(event),
);
if (ownMessage) {
rowChildren.add(avatarOrSizedBox);
} else {
rowChildren.insert(0, avatarOrSizedBox);
}
final row = Row(
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: rowMainAxisAlignment,
children: rowChildren,
);
Widget container;
if (event.hasAggregatedEvents(timeline, RelationshipTypes.reaction)) {
if (event.hasAggregatedEvents(timeline, RelationshipTypes.reaction) ||
displayTime) {
container = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
if (displayTime)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Center(
child: Material(
color: Theme.of(context).backgroundColor,
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
child: Padding(
padding: const EdgeInsets.all(6.0),
child: Text(
event.originServerTs.localizedTime(context),
style: const TextStyle(fontSize: 12),
),
),
)),
),
row,
Padding(
padding: EdgeInsets.only(
top: 4.0,
left: (ownMessage ? 0 : Avatar.defaultSize) + 12.0,
right: (ownMessage ? Avatar.defaultSize : 0) + 12.0,
right: 12.0,
),
child: MessageReactions(event, timeline),
),
@ -238,64 +280,3 @@ class Message extends StatelessWidget {
);
}
}
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, this.timeline, this.displayEvent,
{Key key})
: super(key: key);
@override
Widget build(BuildContext context) {
final displayname = event.sender.calcDisplayname();
final showDisplayname =
!ownMessage && event.senderId != event.room.directChatMatrixID;
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
if (showDisplayname)
Text(
displayname,
style: TextStyle(
fontSize: 10 * AppConfig.fontSizeFactor,
fontWeight: FontWeight.bold,
color: (Theme.of(context).brightness == Brightness.light
? displayname.darkColor
: displayname.lightColor)
.withAlpha(200),
),
),
if (showDisplayname) const SizedBox(width: 4),
Text(
event.originServerTs.localizedTime(context),
style: TextStyle(
color: color.withAlpha(180),
fontSize: 10 * AppConfig.fontSizeFactor,
),
),
if (event.hasAggregatedEvents(timeline, RelationshipTypes.edit))
Padding(
padding: const EdgeInsets.only(left: 2.0),
child: Icon(
Icons.edit_outlined,
size: 12 * AppConfig.fontSizeFactor,
color: color,
),
),
if (ownMessage) const SizedBox(width: 2),
if (ownMessage)
Icon(
displayEvent.statusIcon,
size: 14 * AppConfig.fontSizeFactor,
color: color,
),
],
);
}
}

View file

@ -25,8 +25,10 @@ import 'sticker.dart';
class MessageContent extends StatelessWidget {
final Event event;
final Color textColor;
final void Function(Event) onInfoTab;
const MessageContent(this.event, {Key key, this.textColor}) : super(key: key);
const MessageContent(this.event, {this.onInfoTab, Key key, this.textColor})
: super(key: key);
void _verifyOrRequestKey(BuildContext context) async {
if (event.content['can_request_session'] != true) {
@ -72,8 +74,7 @@ class MessageContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
final fontSize =
DefaultTextStyle.of(context).style.fontSize * AppConfig.fontSizeFactor;
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
switch (event.type) {
case EventTypes.Message:
case EventTypes.Encrypted:
@ -163,14 +164,11 @@ class MessageContent extends StatelessWidget {
continue textmessage;
case MessageTypes.BadEncrypted:
case EventTypes.Encrypted:
return ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).scaffoldBackgroundColor,
onPrimary: Theme.of(context).textTheme.bodyText1.color,
),
return _ButtonContent(
textColor: textColor,
onPressed: () => _verifyOrRequestKey(context),
icon: const Icon(Icons.lock_outline),
label: Text(L10n.of(context).encrypted),
label: L10n.of(context).encrypted,
);
case MessageTypes.Location:
final geoUri =
@ -213,34 +211,20 @@ class MessageContent extends StatelessWidget {
textmessage:
default:
if (event.content['msgtype'] == Matrix.callNamespace) {
return ElevatedButton.icon(
style: ElevatedButton.styleFrom(
primary: Theme.of(context).scaffoldBackgroundColor,
onPrimary: Theme.of(context).textTheme.bodyText1.color,
),
return _ButtonContent(
onPressed: () => launch(event.body),
icon: const Icon(Icons.phone_outlined, color: Colors.green),
label: Text(L10n.of(context).videoCall),
label: L10n.of(context).videoCall,
textColor: textColor,
);
}
if (event.redacted) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.delete_forever_outlined, color: textColor),
const SizedBox(width: 4),
Text(
event.getLocalizedBody(MatrixLocals(L10n.of(context)),
hideReply: true),
style: TextStyle(
color: textColor,
fontSize: fontSize,
fontWeight: FontWeight.bold,
decoration: TextDecoration.lineThrough,
decorationThickness: 0.5,
),
),
],
return _ButtonContent(
label: L10n.of(context)
.redactedAnEvent(event.sender.calcDisplayname()),
icon: const Icon(Icons.delete_outlined),
textColor: textColor,
onPressed: () => onInfoTab(event),
);
}
final bigEmotes = event.onlyEmotes &&
@ -264,15 +248,42 @@ class MessageContent extends StatelessWidget {
}
break;
default:
return Text(
L10n.of(context)
return _ButtonContent(
label: L10n.of(context)
.userSentUnknownEvent(event.sender.calcDisplayname(), event.type),
style: TextStyle(
color: textColor,
decoration: event.redacted ? TextDecoration.lineThrough : null,
),
icon: const Icon(Icons.info_outlined),
textColor: textColor,
onPressed: () => onInfoTab(event),
);
}
return Container(); // else flutter analyze complains
}
}
class _ButtonContent extends StatelessWidget {
final void Function() onPressed;
final String label;
final Icon icon;
final Color textColor;
const _ButtonContent({
@required this.label,
@required this.icon,
@required this.textColor,
@required this.onPressed,
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return OutlinedButton.icon(
style: OutlinedButton.styleFrom(
primary: textColor,
textStyle: TextStyle(color: textColor),
),
onPressed: onPressed,
icon: icon,
label: Text(label),
);
}
}

View file

@ -6,6 +6,7 @@ import 'package:characters/characters.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
@ -100,13 +101,12 @@ class _Reaction extends StatelessWidget {
Widget build(BuildContext context) {
final borderColor = reacted
? Theme.of(context).primaryColor
: Theme.of(context).secondaryHeaderColor;
: Theme.of(context).dividerColor;
final textColor = Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black;
final color = Theme.of(context).scaffoldBackgroundColor;
final fontSize = DefaultTextStyle.of(context).style.fontSize;
final padding = fontSize / 5;
Widget content;
if (reactionKey.startsWith('mxc://')) {
final src = Uri.parse(reactionKey)?.getThumbnail(
@ -122,7 +122,7 @@ class _Reaction extends StatelessWidget {
imageUrl: src.toString(),
height: fontSize,
),
Container(width: 4),
const SizedBox(width: 4),
Text(count.toString(),
style: TextStyle(
color: textColor,
@ -144,6 +144,7 @@ class _Reaction extends StatelessWidget {
return InkWell(
onTap: () => onTap != null ? onTap() : null,
onLongPress: () => onLongPress != null ? onLongPress() : null,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
child: Container(
decoration: BoxDecoration(
color: color,
@ -151,9 +152,9 @@ class _Reaction extends StatelessWidget {
width: 1,
color: borderColor,
),
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
),
padding: EdgeInsets.all(padding),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
child: content,
),
);

View file

@ -22,8 +22,7 @@ class ReplyContent extends StatelessWidget {
final displayEvent = replyEvent != null && timeline != null
? replyEvent.getDisplayEvent(timeline)
: replyEvent;
final fontSize =
DefaultTextStyle.of(context).style.fontSize * AppConfig.fontSizeFactor;
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
if (displayEvent != null &&
AppConfig.renderHtml &&
[EventTypes.Message, EventTypes.Encrypted]

View file

@ -31,11 +31,8 @@ class StateMessage extends StatelessWidget {
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor,
),
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
),
child: Column(
mainAxisSize: MainAxisSize.min,
@ -44,8 +41,7 @@ class StateMessage extends StatelessWidget {
event.getLocalizedBody(MatrixLocals(L10n.of(context))),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyText1.fontSize *
AppConfig.fontSizeFactor,
fontSize: Theme.of(context).textTheme.bodyText1.fontSize,
color: Theme.of(context).textTheme.bodyText2.color,
decoration:
event.redacted ? TextDecoration.lineThrough : null,