small redesign of the image view
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run

This commit is contained in:
otsmr 2026-05-16 20:37:34 +02:00
parent 68c99c271f
commit 32231d11c2
9 changed files with 132 additions and 163 deletions

View file

@ -441,6 +441,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
await mc.cameraController!.setZoomLevel( await mc.cameraController!.setZoomLevel(
mc.selectedCameraDetails.scaleFactor, mc.selectedCameraDetails.scaleFactor,
); );
if (!userService.currentUser.hasZoomed) {
await UserService.update((u) => u.hasZoomed = true);
}
} }
Future<void> pickImageFromGallery() async { Future<void> pickImageFromGallery() async {
@ -613,12 +616,6 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
if (mounted) showSnackbar(context, 'Error: $e'); if (mounted) showSnackbar(context, 'Error: $e');
} }
void _incrementZoomUsageCount() {
if (!userService.currentUser.hasZoomed) {
UserService.update((u) => u.hasZoomed = true);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (mc.selectedCameraDetails.cameraId >= AppEnvironment.cameras.length || if (mc.selectedCameraDetails.cameraId >= AppEnvironment.cameras.length ||
@ -666,19 +663,9 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
}, },
onLongPressEnd: (a) { onLongPressEnd: (a) {
stopVideoRecording(); stopVideoRecording();
if ((mc.selectedCameraDetails.scaleFactor - _baseScaleFactor)
.abs() >
0.05) {
_incrementZoomUsageCount();
}
}, },
onPanEnd: (a) { onPanEnd: (a) {
stopVideoRecording(); stopVideoRecording();
if ((mc.selectedCameraDetails.scaleFactor - _baseScaleFactor)
.abs() >
0.05) {
_incrementZoomUsageCount();
}
}, },
onPanUpdate: onPanUpdate, onPanUpdate: onPanUpdate,
child: Stack( child: Stack(

View file

@ -101,15 +101,16 @@ class _ChatListEntryState extends State<ChatListEntry> {
setState(() {}); setState(() {});
} }
Widget? _getChatEntry(BorderRadius borderRadius, int reactionsForWidth) { Widget? _getChatEntry(
BorderRadius borderRadius,
int reactionsForWidth,
BubbleInfo info,
) {
if (widget.message.type == MessageType.text.name) { if (widget.message.type == MessageType.text.name) {
return ChatTextEntry( return ChatTextEntry(
message: widget.message, message: widget.message,
nextMessage: widget.nextMessage,
prevMessage: widget.prevMessage,
userIdToContact: widget.userIdToContact,
borderRadius: borderRadius, borderRadius: borderRadius,
minWidth: reactionsForWidth * 43, info: info,
); );
} }
@ -118,12 +119,9 @@ class _ChatListEntryState extends State<ChatListEntry> {
if (mediaService!.mediaFile.type == MediaType.audio) { if (mediaService!.mediaFile.type == MediaType.audio) {
return ChatAudioEntry( return ChatAudioEntry(
message: widget.message, message: widget.message,
nextMessage: widget.nextMessage,
prevMessage: widget.prevMessage,
mediaService: mediaService!, mediaService: mediaService!,
userIdToContact: widget.userIdToContact,
borderRadius: borderRadius, borderRadius: borderRadius,
minWidth: reactionsForWidth * 43, info: info,
); );
} }
return ChatMediaEntry( return ChatMediaEntry(
@ -131,7 +129,8 @@ class _ChatListEntryState extends State<ChatListEntry> {
group: widget.group, group: widget.group,
mediaService: mediaService!, mediaService: mediaService!,
galleryItems: widget.galleryItems, galleryItems: widget.galleryItems,
minWidth: reactionsForWidth * 43, borderRadius: borderRadius,
info: info,
); );
} }
@ -168,6 +167,15 @@ class _ChatListEntryState extends State<ChatListEntry> {
.length; .length;
if (reactionsForWidth > 4) reactionsForWidth = 4; if (reactionsForWidth > 4) reactionsForWidth = 4;
final info = getBubbleInfo(
context,
widget.message,
widget.nextMessage,
widget.prevMessage,
widget.userIdToContact,
reactionsForWidth * 43.0,
);
Widget child = Stack( Widget child = Stack(
// overflow: Overflow.visible, // overflow: Overflow.visible,
// clipBehavior: Clip.none, // clipBehavior: Clip.none,
@ -176,11 +184,8 @@ class _ChatListEntryState extends State<ChatListEntry> {
if (widget.message.isDeletedFromSender) if (widget.message.isDeletedFromSender)
ChatTextEntry( ChatTextEntry(
message: widget.message, message: widget.message,
nextMessage: widget.nextMessage,
prevMessage: widget.prevMessage,
userIdToContact: widget.userIdToContact,
borderRadius: borderRadius, borderRadius: borderRadius,
minWidth: reactionsForWidth * 43, info: info,
) )
else else
Column( Column(
@ -191,7 +196,7 @@ class _ChatListEntryState extends State<ChatListEntry> {
mediaService: mediaService, mediaService: mediaService,
borderRadius: borderRadius, borderRadius: borderRadius,
scrollToMessage: widget.scrollToMessage, scrollToMessage: widget.scrollToMessage,
child: _getChatEntry(borderRadius, reactionsForWidth), child: _getChatEntry(borderRadius, reactionsForWidth, info),
), ),
if (reactionsForWidth > 0) const SizedBox(height: 20, width: 10), if (reactionsForWidth > 0) const SizedBox(height: 20, width: 10),
], ],

View file

@ -13,22 +13,16 @@ import 'package:twonly/src/visual/views/chats/chat_messages_components/message_s
class ChatAudioEntry extends StatelessWidget { class ChatAudioEntry extends StatelessWidget {
const ChatAudioEntry({ const ChatAudioEntry({
required this.message, required this.message,
required this.nextMessage,
required this.mediaService, required this.mediaService,
required this.prevMessage,
required this.borderRadius, required this.borderRadius,
required this.userIdToContact, required this.info,
required this.minWidth,
super.key, super.key,
}); });
final Message message; final Message message;
final MediaFileService mediaService; final MediaFileService mediaService;
final Message? nextMessage;
final Message? prevMessage;
final Map<int, Contact>? userIdToContact;
final BorderRadius borderRadius; final BorderRadius borderRadius;
final double minWidth; final BubbleInfo info;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -36,14 +30,6 @@ class ChatAudioEntry extends StatelessWidget {
!mediaService.originalPath.existsSync()) { !mediaService.originalPath.existsSync()) {
return Container(); // media file was purged return Container(); // media file was purged
} }
final info = getBubbleInfo(
context,
message,
nextMessage,
prevMessage,
userIdToContact,
minWidth,
);
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
@ -63,12 +49,7 @@ class ChatAudioEntry extends StatelessWidget {
maxWidth: MediaQuery.of(context).size.width * 0.8, maxWidth: MediaQuery.of(context).size.width * 0.8,
minWidth: 250, minWidth: 250,
), ),
padding: const EdgeInsets.only( padding: info.padding,
left: 10,
top: 6,
bottom: 6,
right: 10,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: info.color, color: info.color,
borderRadius: borderRadius, borderRadius: borderRadius,

View file

@ -26,15 +26,17 @@ class ChatMediaEntry extends StatefulWidget {
required this.group, required this.group,
required this.galleryItems, required this.galleryItems,
required this.mediaService, required this.mediaService,
required this.minWidth, required this.borderRadius,
required this.info,
super.key, super.key,
}); });
final Message message; final Message message;
final double minWidth;
final Group group; final Group group;
final List<MemoryItem> galleryItems; final List<MemoryItem> galleryItems;
final MediaFileService mediaService; final MediaFileService mediaService;
final BorderRadius borderRadius;
final BubbleInfo info;
@override @override
State<ChatMediaEntry> createState() => _ChatMediaEntryState(); State<ChatMediaEntry> createState() => _ChatMediaEntryState();
@ -116,52 +118,34 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
context, context,
); );
var imageBorderRadius = BorderRadius.circular(12); var imageBorderRadius = widget.borderRadius;
Widget additionalMessageData = Container(); Widget additionalMessageData = Container();
final addData = widget.message.additionalMessageData; final addData = widget.message.additionalMessageData;
if (addData != null) { if (addData != null) {
final info = getBubbleInfo(
context,
widget.message,
null,
null,
null,
200,
);
final data = AdditionalMessageData.fromBuffer(addData); final data = AdditionalMessageData.fromBuffer(addData);
if (data.hasLink() && widget.message.mediaStored) { if (data.hasLink() && widget.message.mediaStored) {
imageBorderRadius = const BorderRadius.only( imageBorderRadius = widget.borderRadius.copyWith(
topLeft: Radius.circular(12), bottomLeft: const Radius.circular(5),
topRight: Radius.circular(12), bottomRight: const Radius.circular(5),
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5),
); );
additionalMessageData = Container( additionalMessageData = Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.8, maxWidth: MediaQuery.of(context).size.width * 0.8,
), ),
padding: const EdgeInsets.only( padding: widget.info.padding,
left: 10,
top: 6,
bottom: 6,
right: 10,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: info.color, color: widget.info.color,
borderRadius: const BorderRadius.only( borderRadius: widget.borderRadius.copyWith(
topLeft: Radius.circular(5), topLeft: const Radius.circular(5),
topRight: Radius.circular(12),
bottomLeft: Radius.circular(12),
bottomRight: Radius.circular(12),
), ),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
BetterText(text: data.link, textColor: info.textColor), BetterText(text: data.link, textColor: widget.info.textColor),
], ],
), ),
); );
@ -178,7 +162,12 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
onDoubleTap: onDoubleTap, onDoubleTap: onDoubleTap,
onTap: (widget.message.type == MessageType.media.name) ? onTap : null, onTap: (widget.message.type == MessageType.media.name) ? onTap : null,
child: SizedBox( child: SizedBox(
width: (widget.minWidth > 150) ? widget.minWidth : 150, width: (widget.info.minWidth > 150)
? widget.info.minWidth
: (widget.message.mediaStored &&
widget.mediaService.imagePreviewAvailable)
? 150
: null,
height: height:
(widget.message.mediaStored && (widget.message.mediaStored &&
widget.mediaService.imagePreviewAvailable) widget.mediaService.imagePreviewAvailable)
@ -195,6 +184,8 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
color: color, color: color,
galleryItems: widget.galleryItems, galleryItems: widget.galleryItems,
canBeReopened: _canBeReopened, canBeReopened: _canBeReopened,
borderRadius: imageBorderRadius,
info: widget.info,
), ),
), ),
), ),

View file

@ -8,20 +8,14 @@ import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/f
class ChatTextEntry extends StatelessWidget { class ChatTextEntry extends StatelessWidget {
const ChatTextEntry({ const ChatTextEntry({
required this.message, required this.message,
required this.nextMessage,
required this.prevMessage,
required this.borderRadius, required this.borderRadius,
required this.userIdToContact, required this.info,
required this.minWidth,
super.key, super.key,
}); });
final Message message; final Message message;
final Message? nextMessage;
final Message? prevMessage;
final Map<int, Contact>? userIdToContact;
final BorderRadius borderRadius; final BorderRadius borderRadius;
final double minWidth; final BubbleInfo info;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -40,15 +34,6 @@ class ChatTextEntry extends StatelessWidget {
); );
} }
final info = getBubbleInfo(
context,
message,
nextMessage,
prevMessage,
userIdToContact,
minWidth,
);
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
final textWidth = measureTextWidth(info.text); final textWidth = measureTextWidth(info.text);
@ -65,14 +50,9 @@ class ChatTextEntry extends StatelessWidget {
return Container( return Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.8, maxWidth: MediaQuery.of(context).size.width * 0.8,
minWidth: minWidth, minWidth: info.minWidth,
),
padding: const EdgeInsets.only(
left: 10,
top: 6,
bottom: 6,
right: 10,
), ),
padding: info.padding,
decoration: BoxDecoration( decoration: BoxDecoration(
color: info.color, color: info.color,
borderRadius: borderRadius, borderRadius: borderRadius,

View file

@ -14,6 +14,8 @@ class BubbleInfo {
late Color color; late Color color;
late bool expanded; late bool expanded;
late double spacerWidth; late double spacerWidth;
late EdgeInsets padding;
late double minWidth;
} }
BubbleInfo getBubbleInfo( BubbleInfo getBubbleInfo(
@ -29,7 +31,11 @@ BubbleInfo getBubbleInfo(
..textColor = Colors.white ..textColor = Colors.white
..color = getMessageColor(message.senderId != null) ..color = getMessageColor(message.senderId != null)
..displayTime = !combineTextMessageWithNext(message, nextMessage) ..displayTime = !combineTextMessageWithNext(message, nextMessage)
..displayUserName = ''; ..displayUserName = ''
..minWidth = minWidth
..padding = message.type == MessageType.media.name
? const EdgeInsets.symmetric(horizontal: 10, vertical: 2)
: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10);
if (message.senderId != null && if (message.senderId != null &&
userIdToContact != null && userIdToContact != null &&
@ -86,11 +92,10 @@ double measureTextWidth(
} }
bool combineTextMessageWithNext(Message message, Message? nextMessage) { bool combineTextMessageWithNext(Message message, Message? nextMessage) {
if (nextMessage != null && nextMessage.content != null) { if (nextMessage != null) {
if (nextMessage.senderId == message.senderId) { if (nextMessage.senderId == message.senderId) {
if (nextMessage.type == MessageType.text.name && if (nextMessage.content == null ||
message.type == MessageType.text.name) { !EmojiAnimationComp.supported(nextMessage.content!)) {
if (!EmojiAnimationComp.supported(nextMessage.content!)) {
final diff = nextMessage.createdAt final diff = nextMessage.createdAt
.difference(message.createdAt) .difference(message.createdAt)
.inMinutes; .inMinutes;
@ -100,6 +105,5 @@ bool combineTextMessageWithNext(Message message, Message? nextMessage) {
} }
} }
} }
}
return false; return false;
} }

View file

@ -6,17 +6,21 @@ import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/utils/misc.dart';
class FriendlyMessageTime extends StatelessWidget { class FriendlyMessageTime extends StatelessWidget {
const FriendlyMessageTime({required this.message, super.key}); const FriendlyMessageTime({
required this.message,
this.color,
super.key,
});
final Message message; final Message message;
final Color? color;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Align( return Padding(
alignment: AlignmentGeometry.centerRight,
child: Padding(
padding: const EdgeInsets.only(left: 6), padding: const EdgeInsets.only(left: 6),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min,
children: [ children: [
if (message.modifiedAt != null && !message.isDeletedFromSender) if (message.modifiedAt != null && !message.isDeletedFromSender)
Padding( Padding(
@ -25,7 +29,7 @@ class FriendlyMessageTime extends StatelessWidget {
height: 10, height: 10,
child: FaIcon( child: FaIcon(
FontAwesomeIcons.pencil, FontAwesomeIcons.pencil,
color: Colors.white.withAlpha(150), color: color ?? Colors.white.withAlpha(150),
size: 10, size: 10,
), ),
), ),
@ -39,14 +43,13 @@ class FriendlyMessageTime extends StatelessWidget {
), ),
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
color: Colors.white.withAlpha(150), color: color ?? Colors.white.withAlpha(150),
decoration: TextDecoration.none, decoration: TextDecoration.none,
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
), ),
), ),
], ],
), ),
),
); );
} }
} }

View file

@ -5,6 +5,9 @@ import 'package:twonly/locator.dart';
import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/database/twonly.db.dart';
import 'package:twonly/src/model/memory_item.model.dart'; import 'package:twonly/src/model/memory_item.model.dart';
import 'package:twonly/src/services/mediafiles/mediafile.service.dart'; import 'package:twonly/src/services/mediafiles/mediafile.service.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/common.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/entries/friendly_message_time.comp.dart';
import 'package:twonly/src/visual/views/chats/chat_messages_components/message_send_state_icon.dart'; import 'package:twonly/src/visual/views/chats/chat_messages_components/message_send_state_icon.dart';
import 'package:twonly/src/visual/views/memories/components/memory_thumbnail.comp.dart'; import 'package:twonly/src/visual/views/memories/components/memory_thumbnail.comp.dart';
import 'package:twonly/src/visual/views/memories/synchronized_viewer.view.dart'; import 'package:twonly/src/visual/views/memories/synchronized_viewer.view.dart';
@ -17,6 +20,8 @@ class InChatMediaViewer extends StatefulWidget {
required this.color, required this.color,
required this.galleryItems, required this.galleryItems,
required this.canBeReopened, required this.canBeReopened,
required this.borderRadius,
required this.info,
super.key, super.key,
}); });
@ -26,6 +31,8 @@ class InChatMediaViewer extends StatefulWidget {
final List<MemoryItem> galleryItems; final List<MemoryItem> galleryItems;
final Color color; final Color color;
final bool canBeReopened; final bool canBeReopened;
final BorderRadius borderRadius;
final BubbleInfo info;
@override @override
State<InChatMediaViewer> createState() => _InChatMediaViewerState(); State<InChatMediaViewer> createState() => _InChatMediaViewerState();
@ -147,22 +154,33 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
minHeight: 39, minHeight: 39,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: widget.info.color.withValues(alpha: 0.3),
border: Border.all( border: Border.all(
color: widget.color, color: widget.info.color.withValues(alpha: 0.4),
), ),
borderRadius: BorderRadius.circular(12), borderRadius: widget.borderRadius,
), ),
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: widget.info.padding,
vertical: (widget.canBeReopened) ? 5 : 10.0, child: Row(
horizontal: 4, children: [
), MessageSendStateIcon(
child: MessageSendStateIcon(
[widget.message], [widget.message],
[widget.mediaService.mediaFile], [widget.mediaService.mediaFile],
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: widget.message.senderId == null
? MainAxisAlignment.end
: MainAxisAlignment.start,
canBeReopened: widget.canBeReopened, canBeReopened: widget.canBeReopened,
), ),
if (widget.info.displayTime || widget.message.modifiedAt != null)
FriendlyMessageTime(
message: widget.message,
color: isDarkMode(context)
? Colors.white.withAlpha(100)
: Colors.black.withAlpha(100),
),
],
),
), ),
); );
} }
@ -172,7 +190,7 @@ class _InChatMediaViewerState extends State<InChatMediaViewer> {
color: Colors.transparent, color: Colors.transparent,
), ),
color: Colors.transparent, color: Colors.transparent,
borderRadius: BorderRadius.circular(12), borderRadius: widget.borderRadius,
), ),
child: galleryItemIndex != null child: galleryItemIndex != null
? MemoriesThumbnailComp( ? MemoriesThumbnailComp(

View file

@ -411,7 +411,7 @@ class MemoriesViewState extends State<MemoriesView> {
strokeWidth: 2.5, strokeWidth: 2.5,
color: context.color.primary, color: context.color.primary,
backgroundColor: context.color.primary backgroundColor: context.color.primary
.withOpacity(0.2), .withValues(alpha: 0.2),
), ),
), ),
), ),