smaller ui fixes

This commit is contained in:
otsmr 2026-05-16 19:13:42 +02:00
parent 91eedc76b0
commit 68c99c271f
6 changed files with 187 additions and 129 deletions

View file

@ -47,21 +47,15 @@ Future<bool> 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<bool> 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);

View file

@ -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<MemoryItem> galleryItems;
final List<String> months;
final Map<String, List<int>> orderedByMonth;
final Map<int, List<MemoryItem>> 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<MemoryItem>? galleryItems,
List<String>? months,
Map<String, List<int>>? 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 &&

View file

@ -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),
],
),
],
),
],
),
);
},
);
}
}

View file

@ -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),
],
),
],
),
],
),
);
},
);
}
}

View file

@ -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

View file

@ -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<MemoriesView> {
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<MemoriesView> {
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