Compare commits
4 Commits
ad2b10972c
...
a783aab229
Author | SHA1 | Date | |
---|---|---|---|
a783aab229 | |||
43dc2285b3 | |||
eac8e3fb44 | |||
035d29fabc |
@ -483,6 +483,9 @@ class HttpFileTransferService {
|
||||
mime: mime,
|
||||
);
|
||||
final newConv = conv.copyWith(
|
||||
lastMessage: conv.lastMessage?.id == job.mId ?
|
||||
msg :
|
||||
conv.lastMessage,
|
||||
sharedMedia: [
|
||||
sharedMedium,
|
||||
...conv.sharedMedia,
|
||||
|
@ -100,11 +100,11 @@ class NotificationsService {
|
||||
title: c.title,
|
||||
body: body,
|
||||
largeIcon: c.avatarUrl.isNotEmpty ? 'file://${c.avatarUrl}' : null,
|
||||
notificationLayout: m.thumbnailable ?
|
||||
notificationLayout: m.isThumbnailable ?
|
||||
NotificationLayout.BigPicture :
|
||||
NotificationLayout.Messaging,
|
||||
category: NotificationCategory.Message,
|
||||
bigPicture: m.thumbnailable ? 'file://${m.mediaUrl}' : null,
|
||||
bigPicture: m.isThumbnailable ? 'file://${m.mediaUrl}' : null,
|
||||
payload: <String, String>{
|
||||
'conversationJid': c.jid,
|
||||
'sid': m.sid,
|
||||
|
@ -201,19 +201,46 @@ String? guessMimeTypeFromExtension(String ext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Return the translated name describing the MIME type [mime]. If [mime] is null or
|
||||
/// the MIME type is neither image, video or audio, then it falls back to the
|
||||
/// translation of "file".
|
||||
String mimeTypeToName(String? mime) {
|
||||
if (mime != null) {
|
||||
if (mime.startsWith('image')) {
|
||||
return t.messages.image;
|
||||
} else if (mime.startsWith('audio')) {
|
||||
return t.messages.audio;
|
||||
} else if (mime.startsWith('video')) {
|
||||
return t.messages.video;
|
||||
}
|
||||
}
|
||||
|
||||
return t.messages.file;
|
||||
}
|
||||
|
||||
/// Return an emoji for the MIME type [mime]. If [addTypeName] id true, then a human readable
|
||||
/// name for the MIME type will be appended.
|
||||
String mimeTypeToEmoji(String? mime, {bool addTypeName = true}) {
|
||||
String value;
|
||||
if (mime != null) {
|
||||
if (mime.startsWith('image')) {
|
||||
return '🖼️${addTypeName ? " ${t.messages.image}" : ""}';
|
||||
value = '🖼️';
|
||||
} else if (mime.startsWith('audio')) {
|
||||
return '🎙${addTypeName ? " ${t.messages.audio}" : ""}';
|
||||
value = '🎙';
|
||||
} else if (mime.startsWith('video')) {
|
||||
return '🎬${addTypeName ? " ${t.messages.video}" : ""}';
|
||||
value = '🎬';
|
||||
} else {
|
||||
value = '📁';
|
||||
}
|
||||
} else {
|
||||
value = '📁';
|
||||
}
|
||||
return '📁${addTypeName ? " ${t.messages.file}" : ""}';
|
||||
|
||||
if (addTypeName) {
|
||||
value += ' ${mimeTypeToName(mime)}';
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// Parse an Uri and return the "filename".
|
||||
|
@ -150,7 +150,7 @@ class Message with _$Message {
|
||||
|
||||
/// Returns true if the message contains media that can be thumbnailed, i.e. videos or
|
||||
/// images.
|
||||
bool get thumbnailable => isMedia && mediaType != null && (
|
||||
bool get isThumbnailable => isMedia && mediaType != null && (
|
||||
mediaType!.startsWith('image/') ||
|
||||
mediaType!.startsWith('video/')
|
||||
);
|
||||
|
@ -41,7 +41,7 @@ class SendFilesPage extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(right: 4),
|
||||
child: SharedImageWidget(
|
||||
path,
|
||||
() {
|
||||
onTap: () {
|
||||
if (selected) {
|
||||
// The trash can icon has been tapped
|
||||
context.read<SendFilesBloc>().add(
|
||||
|
@ -182,9 +182,14 @@ Widget buildQuoteMessageWidget(Message message, bool sent, { void Function()? re
|
||||
|
||||
Widget buildSharedMediaWidget(SharedMedium medium, String conversationJid) {
|
||||
if (medium.mime == null) {
|
||||
return SharedFileWidget(medium.path);
|
||||
return SharedFileWidget(
|
||||
medium.path,
|
||||
);
|
||||
} else if (medium.mime!.startsWith('image/')) {
|
||||
return SharedImageWidget(medium.path, () => OpenFile.open(medium.path));
|
||||
return SharedImageWidget(
|
||||
medium.path,
|
||||
onTap: () => OpenFile.open(medium.path),
|
||||
);
|
||||
} else if (medium.mime!.startsWith('video/')) {
|
||||
return SharedVideoWidget(
|
||||
medium.path,
|
||||
|
@ -23,22 +23,34 @@ const sharedMediaContainerDimension = 75.0;
|
||||
|
||||
/// A widget to show a message that was sent within a chat or is about to be sent.
|
||||
class SharedMediaContainer extends StatelessWidget {
|
||||
const SharedMediaContainer(this.child, { this.onTap, super.key });
|
||||
const SharedMediaContainer(this.child, {
|
||||
this.onTap,
|
||||
this.size = sharedMediaContainerDimension,
|
||||
super.key,
|
||||
}
|
||||
);
|
||||
final Widget? child;
|
||||
final void Function()? onTap;
|
||||
final double size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: SizedBox(
|
||||
height: sharedMediaContainerDimension,
|
||||
width: sharedMediaContainerDimension,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: child,
|
||||
),
|
||||
final childWidget = SizedBox(
|
||||
height: size,
|
||||
width: size,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
|
||||
if (onTap != null) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: childWidget,
|
||||
);
|
||||
}
|
||||
|
||||
return childWidget;
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,29 @@ import 'package:flutter/material.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/shared/base.dart';
|
||||
|
||||
class SharedImageWidget extends StatelessWidget {
|
||||
const SharedImageWidget(this.path, this.onTap, { this.borderColor, this.child, super.key });
|
||||
const SharedImageWidget(
|
||||
this.path, {
|
||||
this.onTap,
|
||||
this.borderColor,
|
||||
this.child,
|
||||
this.borderRadius = 10,
|
||||
this.size = sharedMediaContainerDimension,
|
||||
super.key,
|
||||
}
|
||||
);
|
||||
final String path;
|
||||
final Color? borderColor;
|
||||
final void Function() onTap;
|
||||
final void Function()? onTap;
|
||||
final Widget? child;
|
||||
final double borderRadius;
|
||||
final double size;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SharedMediaContainer(
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
border: borderColor != null ? Border.all(
|
||||
color: borderColor!,
|
||||
width: 4,
|
||||
@ -27,6 +38,7 @@ class SharedImageWidget extends StatelessWidget {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: child,
|
||||
),
|
||||
size: size,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import 'package:moxxyv2/shared/models/conversation.dart';
|
||||
import 'package:moxxyv2/ui/constants.dart';
|
||||
import 'package:moxxyv2/ui/service/data.dart';
|
||||
import 'package:moxxyv2/ui/widgets/avatar.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/shared/image.dart';
|
||||
import 'package:moxxyv2/ui/widgets/chat/typing.dart';
|
||||
|
||||
class ConversationsListRow extends StatefulWidget {
|
||||
@ -85,12 +86,21 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
return const TypingIndicatorWidget(Colors.black, Colors.white);
|
||||
}
|
||||
|
||||
final lastMessage = widget.conversation.lastMessage;
|
||||
String body;
|
||||
if (widget.conversation.lastMessage == null) {
|
||||
if (lastMessage == null) {
|
||||
body = '';
|
||||
} else {
|
||||
if (widget.conversation.lastMessage!.isRetracted) {
|
||||
if (lastMessage.isRetracted) {
|
||||
body = t.messages.retracted;
|
||||
} else if (lastMessage.isMedia) {
|
||||
// If the file is thumbnailable, we display a small preview on the left of the
|
||||
// body, so we don't need the emoji then.
|
||||
if (lastMessage.isThumbnailable) {
|
||||
body = mimeTypeToName(lastMessage.mediaType);
|
||||
} else {
|
||||
body = mimeTypeToEmoji(lastMessage.mediaType);
|
||||
}
|
||||
} else {
|
||||
body = widget.conversation.lastMessage!.body;
|
||||
}
|
||||
@ -183,26 +193,41 @@ class ConversationsListRowState extends State<ConversationsListRow> {
|
||||
],
|
||||
),
|
||||
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
LimitedBox(
|
||||
maxWidth: textWidth,
|
||||
child: _buildLastMessageBody(),
|
||||
),
|
||||
const Spacer(),
|
||||
Visibility(
|
||||
visible: showBadge,
|
||||
child: Badge(
|
||||
badgeContent: Text(badgeText),
|
||||
badgeColor: bubbleColorSent,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
...widget.conversation.lastMessage?.isThumbnailable == true ? [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 5),
|
||||
child: SharedImageWidget(
|
||||
widget.conversation.lastMessage!.mediaUrl!,
|
||||
borderRadius: 5,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
] : [
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
LimitedBox(
|
||||
maxWidth: textWidth,
|
||||
child: _buildLastMessageBody(),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: sentBySelf,
|
||||
child: _getLastMessageIcon(),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
Visibility(
|
||||
visible: showBadge,
|
||||
child: Badge(
|
||||
badgeContent: Text(badgeText),
|
||||
badgeColor: bubbleColorSent,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: sentBySelf,
|
||||
child: _getLastMessageIcon(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user