diff --git a/lib/src/services/mediafiles/thumbnail.service.dart b/lib/src/services/mediafiles/thumbnail.service.dart index 2b3122ec..2e2209de 100644 --- a/lib/src/services/mediafiles/thumbnail.service.dart +++ b/lib/src/services/mediafiles/thumbnail.service.dart @@ -47,21 +47,15 @@ Future createThumbnailsForImage( ) async { final stopwatch = Stopwatch()..start(); - if (destinationFile.existsSync()) { - return true; - } - try { - final bytes = sourceFile.readAsBytesSync(); - final result = await FlutterImageCompress.compressWithList( - bytes, - minWidth: 200, - minHeight: 200, - quality: 70, + await FlutterImageCompress.compressAndGetFile( + sourceFile.absolute.path, + destinationFile.absolute.path, + minWidth: 300, + minHeight: 300, + quality: 100, format: CompressFormat.webp, ); - - destinationFile.writeAsBytesSync(result); stopwatch.stop(); Log.info( 'It took ${stopwatch.elapsedMilliseconds}ms to create the image thumbnail.', @@ -94,15 +88,15 @@ Future createThumbnailsForGif( final thumbnail = img.copyResize( image, - width: image.width > image.height ? 200 : null, - height: image.height >= image.width ? 200 : null, + width: image.width > image.height ? 400 : null, + height: image.height >= image.width ? 400 : null, ); final pngBytes = img.encodePng(thumbnail); final webp = await FlutterImageCompress.compressWithList( pngBytes, format: CompressFormat.webp, - quality: 70, + quality: 85, ); destinationFile.writeAsBytesSync(webp); diff --git a/lib/src/services/memories/memories.service.dart b/lib/src/services/memories/memories.service.dart index 26a3016a..b9d65939 100644 --- a/lib/src/services/memories/memories.service.dart +++ b/lib/src/services/memories/memories.service.dart @@ -15,6 +15,7 @@ import 'package:twonly/src/utils/log.dart'; class MemoriesState { const MemoriesState({ required this.filesToMigrate, + required this.totalFilesToMigrate, required this.galleryItems, required this.months, required this.orderedByMonth, @@ -22,16 +23,21 @@ class MemoriesState { }); final int filesToMigrate; + final int totalFilesToMigrate; final List galleryItems; final List months; final Map> orderedByMonth; final Map> galleryItemsLastYears; bool get isLoading => filesToMigrate > 0; + double get migrationProgress => totalFilesToMigrate > 0 + ? (totalFilesToMigrate - filesToMigrate) / totalFilesToMigrate + : 0; bool get isEmpty => galleryItems.isEmpty && filesToMigrate == 0; MemoriesState copyWith({ int? filesToMigrate, + int? totalFilesToMigrate, List? galleryItems, List? months, Map>? orderedByMonth, @@ -39,6 +45,7 @@ class MemoriesState { }) { return MemoriesState( filesToMigrate: filesToMigrate ?? this.filesToMigrate, + totalFilesToMigrate: totalFilesToMigrate ?? this.totalFilesToMigrate, galleryItems: galleryItems ?? this.galleryItems, months: months ?? this.months, orderedByMonth: orderedByMonth ?? this.orderedByMonth, @@ -63,6 +70,7 @@ class MemoriesService { MemoriesState _currentState = const MemoriesState( filesToMigrate: 0, + totalFilesToMigrate: 0, galleryItems: [], months: [], orderedByMonth: {}, @@ -182,6 +190,7 @@ class MemoriesService { return MemoriesState( filesToMigrate: filesToMigrate, + totalFilesToMigrate: filesToMigrate, // Reset total when computing new state? No, keep existing total if migrating. galleryItems: tempGalleryItems, months: tempMonths, orderedByMonth: tempOrderedByMonth, @@ -195,7 +204,11 @@ class MemoriesService { .getAllMediaFilesPendingMigration(); if (pendingFiles.isNotEmpty) { - _updateMigrationCount(pendingFiles.length); + _currentState = _currentState.copyWith( + filesToMigrate: pendingFiles.length, + totalFilesToMigrate: pendingFiles.length, + ); + _notifyState(); for (final mediaFile in pendingFiles) { final mediaService = MediaFileService(mediaFile); @@ -261,7 +274,7 @@ class MemoriesService { mediaFiles: mediaFiles, mediaIdToSender: mediaIdToSenderContact, filesToMigrate: _currentState.filesToMigrate, - ); + ).copyWith(totalFilesToMigrate: _currentState.totalFilesToMigrate); for (final item in newState.galleryItems) { if (!item.mediaService.mediaFile.hasThumbnail && diff --git a/lib/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart b/lib/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart index 91c43a3d..3d3adf89 100644 --- a/lib/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart +++ b/lib/src/visual/views/chats/chat_messages_components/entries/chat_audio_entry.dart @@ -45,55 +45,82 @@ class ChatAudioEntry extends StatelessWidget { minWidth, ); - return Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * 0.8, - minWidth: 250, - ), - padding: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10), - decoration: BoxDecoration( - color: info.color, - borderRadius: borderRadius, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (info.displayUserName != '') - Text( - info.displayUserName, - textAlign: TextAlign.left, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, + return LayoutBuilder( + builder: (context, constraints) { + final textWidth = measureTextWidth(info.text); + const timeWidth = 60.0; + final isExpanded = + info.expanded || + (textWidth + timeWidth + 20 > constraints.maxWidth); + final effectiveSpacerWidth = + constraints.minWidth - textWidth - timeWidth; + final spacerWidth = effectiveSpacerWidth > 0 + ? effectiveSpacerWidth + : 0.0; + + return Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.8, + minWidth: 250, + ), + padding: const EdgeInsets.only( + left: 10, + top: 6, + bottom: 6, + right: 10, + ), + decoration: BoxDecoration( + color: info.color, + borderRadius: borderRadius, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (info.text != '') - Expanded( - child: BetterText(text: info.text, textColor: info.textColor), - ) - else ...[ - if (mediaService.mediaFile.downloadState == - DownloadState.ready || - mediaService.mediaFile.downloadState == null) - mediaService.tempPath.existsSync() - ? InChatAudioPlayer( - path: mediaService.tempPath.path, - message: message, - ) - : Container() - else - MessageSendStateIcon([message], [mediaService.mediaFile]), - ], - if (info.displayTime || message.modifiedAt != null) - FriendlyMessageTime(message: message), + if (info.displayUserName != '') + Text( + info.displayUserName, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (isExpanded && info.text != '') + Expanded( + child: BetterText( + text: info.text, + textColor: info.textColor, + ), + ) + else if (info.text != '') ...[ + BetterText(text: info.text, textColor: info.textColor), + SizedBox(width: spacerWidth), + ] else ...[ + if (mediaService.mediaFile.downloadState == + DownloadState.ready || + mediaService.mediaFile.downloadState == null) + mediaService.tempPath.existsSync() + ? InChatAudioPlayer( + path: mediaService.tempPath.path, + message: message, + ) + : Container() + else + MessageSendStateIcon([message], [mediaService.mediaFile]), + SizedBox(width: spacerWidth), + ], + if (info.displayTime || message.modifiedAt != null) + FriendlyMessageTime(message: message), + ], + ), ], ), - ], - ), + ); + }, ); } } diff --git a/lib/src/visual/views/chats/chat_messages_components/entries/chat_text_entry.dart b/lib/src/visual/views/chats/chat_messages_components/entries/chat_text_entry.dart index 5f9b6bfd..fd1339be 100644 --- a/lib/src/visual/views/chats/chat_messages_components/entries/chat_text_entry.dart +++ b/lib/src/visual/views/chats/chat_messages_components/entries/chat_text_entry.dart @@ -49,48 +49,71 @@ class ChatTextEntry extends StatelessWidget { minWidth, ); - return Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * 0.8, - minWidth: minWidth, - ), - padding: const EdgeInsets.only(left: 10, top: 6, bottom: 6, right: 10), - decoration: BoxDecoration( - color: info.color, - borderRadius: borderRadius, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (info.displayUserName != '') - Text( - info.displayUserName, - textAlign: TextAlign.left, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, + return LayoutBuilder( + builder: (context, constraints) { + final textWidth = measureTextWidth(info.text); + const timeWidth = 60.0; + final isExpanded = + info.expanded || + (textWidth + timeWidth + 20 > constraints.maxWidth); + final effectiveSpacerWidth = + constraints.minWidth - textWidth - timeWidth; + final spacerWidth = effectiveSpacerWidth > 0 + ? effectiveSpacerWidth + : 0.0; + + return Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.8, + minWidth: minWidth, + ), + padding: const EdgeInsets.only( + left: 10, + top: 6, + bottom: 6, + right: 10, + ), + decoration: BoxDecoration( + color: info.color, + borderRadius: borderRadius, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (info.expanded) - Expanded( - child: BetterText(text: info.text, textColor: info.textColor), - ) - else ...[ - BetterText(text: info.text, textColor: info.textColor), - SizedBox( - width: info.spacerWidth, + if (info.displayUserName != '') + Text( + info.displayUserName, + textAlign: TextAlign.left, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), ), - ], - if (info.displayTime || message.modifiedAt != null) - FriendlyMessageTime(message: message), + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (isExpanded) + Expanded( + child: BetterText( + text: info.text, + textColor: info.textColor, + ), + ) + else ...[ + BetterText(text: info.text, textColor: info.textColor), + SizedBox( + width: spacerWidth, + ), + ], + if (info.displayTime || message.modifiedAt != null) + FriendlyMessageTime(message: message), + ], + ), ], ), - ], - ), + ); + }, ); } } diff --git a/lib/src/visual/views/chats/chat_messages_components/entries/common.dart b/lib/src/visual/views/chats/chat_messages_components/entries/common.dart index cf0c01eb..06f8a928 100644 --- a/lib/src/visual/views/chats/chat_messages_components/entries/common.dart +++ b/lib/src/visual/views/chats/chat_messages_components/entries/common.dart @@ -50,10 +50,11 @@ BubbleInfo getBubbleInfo( info.spacerWidth = minWidth - measureTextWidth(info.text) - 53; if (info.spacerWidth < 0) info.spacerWidth = 0; - info.expanded = false; - if (message.quotesMessageId == null) { - info.color = getMessageColor(message.senderId != null); - } + info + ..expanded = false + ..color = message.quotesMessageId != null + ? Colors.transparent + : getMessageColor(message.senderId != null); if (message.isDeletedFromSender) { info ..color = context.color.surfaceBright diff --git a/lib/src/visual/views/memories/memories.view.dart b/lib/src/visual/views/memories/memories.view.dart index 1fc4ee20..43da6b84 100644 --- a/lib/src/visual/views/memories/memories.view.dart +++ b/lib/src/visual/views/memories/memories.view.dart @@ -9,7 +9,6 @@ import 'package:twonly/src/utils/misc.dart'; import 'package:twonly/src/visual/components/alert.dialog.dart'; import 'package:twonly/src/visual/components/draggable_scrollbar.comp.dart'; import 'package:twonly/src/visual/components/snackbar.dart'; -import 'package:twonly/src/visual/loader/three_rotating_dots.loader.dart'; import 'package:twonly/src/visual/views/memories/components/flashback_banner.comp.dart'; import 'package:twonly/src/visual/views/memories/components/memory_thumbnail.comp.dart'; import 'package:twonly/src/visual/views/memories/components/selection_toolbar.comp.dart'; @@ -293,29 +292,6 @@ class MemoriesViewState extends State { builder: (context, snapshot) { final state = snapshot.data ?? _service.currentState; - if (state.isLoading) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ThreeRotatingDots( - size: 40, - color: context.color.primary, - ), - const SizedBox(height: 16), - Text( - context.lang.migrationOfMemories(state.filesToMigrate), - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ); - } - if (state.isEmpty) { return Center( child: Padding( @@ -417,6 +393,30 @@ class MemoriesViewState extends State { elevation: 0, backgroundColor: context.color.surface, actions: [ + if (state.isLoading) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Center( + child: Tooltip( + message: context.lang.migrationOfMemories( + state.filesToMigrate, + ), + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + value: state.migrationProgress, + strokeWidth: 2.5, + color: context.color.primary, + backgroundColor: context.color.primary + .withOpacity(0.2), + ), + ), + ), + ), + ), IconButton( icon: Icon( _filterFavoritesOnly