From 57c73a86ace92276f4e7f137d03414a85b70aac1 Mon Sep 17 00:00:00 2001 From: otsmr Date: Sat, 27 Dec 2025 16:24:31 +0100 Subject: [PATCH] fix #305 --- lib/src/localization/app_de.arb | 4 +- lib/src/localization/app_en.arb | 4 +- .../generated/app_localizations.dart | 12 ++ .../generated/app_localizations_de.dart | 8 ++ .../generated/app_localizations_en.dart | 8 ++ lib/src/views/camera/share_image_view.dart | 4 +- lib/src/views/memories/memories.view.dart | 113 +++++++++++++++--- 7 files changed, 134 insertions(+), 19 deletions(-) diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 1361dac..57d87a7 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -459,5 +459,7 @@ "linkPubkeyDoesNotMatch": "Der öffentliche Schlüssel im Link stimmt nicht mit dem für diesen Kontakt gespeicherten öffentlichen Schlüssel überein. Triff die Person persönlich und scanne den QR-Code direkt!", "startWithCameraOpen": "Mit geöffneter Kamera starten", "showImagePreviewWhenSending": "Bildvorschau bei der Auswahl von Empfängern anzeigen", - "verifiedPublicKey": "Der öffentliche Schlüssel von {username} wurde überprüft und ist gültig." + "verifiedPublicKey": "Der öffentliche Schlüssel von {username} wurde überprüft und ist gültig.", + "memoriesAYearAgo": "Vor einem Jahr", + "memoriesXYearsAgo": "Vor {years} Jahren" } \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 96ce280..ddb53ec 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -489,5 +489,7 @@ "linkPubkeyDoesNotMatch": "The public key in the link does not match the public key stored for this contact. Try to meet your friend in person and scan the QR code directly!", "startWithCameraOpen": "Start with camera open", "showImagePreviewWhenSending": "Display image preview when selecting recipients", - "verifiedPublicKey": "The public key of {username} has been verified and is valid." + "verifiedPublicKey": "The public key of {username} has been verified and is valid.", + "memoriesAYearAgo": "One year ago", + "memoriesXYearsAgo": "{years} years ago" } \ No newline at end of file diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index d337892..d2acbb5 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -2857,6 +2857,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'The public key of {username} has been verified and is valid.'** String verifiedPublicKey(Object username); + + /// No description provided for @memoriesAYearAgo. + /// + /// In en, this message translates to: + /// **'One year ago'** + String get memoriesAYearAgo; + + /// No description provided for @memoriesXYearsAgo. + /// + /// In en, this message translates to: + /// **'{years} years ago'** + String memoriesXYearsAgo(Object years); } class _AppLocalizationsDelegate diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index 61bae51..e15da68 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -1582,4 +1582,12 @@ class AppLocalizationsDe extends AppLocalizations { String verifiedPublicKey(Object username) { return 'Der öffentliche Schlüssel von $username wurde überprüft und ist gültig.'; } + + @override + String get memoriesAYearAgo => 'Vor einem Jahr'; + + @override + String memoriesXYearsAgo(Object years) { + return 'Vor $years Jahren'; + } } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index 04d56d9..6d119c3 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -1572,4 +1572,12 @@ class AppLocalizationsEn extends AppLocalizations { String verifiedPublicKey(Object username) { return 'The public key of $username has been verified and is valid.'; } + + @override + String get memoriesAYearAgo => 'One year ago'; + + @override + String memoriesXYearsAgo(Object years) { + return '$years years ago'; + } } diff --git a/lib/src/views/camera/share_image_view.dart b/lib/src/views/camera/share_image_view.dart index 6ee9eed..db23ac3 100644 --- a/lib/src/views/camera/share_image_view.dart +++ b/lib/src/views/camera/share_image_view.dart @@ -254,10 +254,10 @@ class _ShareImageView extends State { border: Border.all(color: context.color.primary, width: 3), color: context.color.primary, - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(12), ), child: ClipRRect( - borderRadius: BorderRadius.circular(7), + borderRadius: BorderRadius.circular(12), child: Image.memory(_imageBytes!), ), ), diff --git a/lib/src/views/memories/memories.view.dart b/lib/src/views/memories/memories.view.dart index 3d48f82..8125f4a 100644 --- a/lib/src/views/memories/memories.view.dart +++ b/lib/src/views/memories/memories.view.dart @@ -18,12 +18,13 @@ class MemoriesView extends StatefulWidget { } class MemoriesViewState extends State { - bool verticalGallery = false; List galleryItems = []; Map> orderedByMonth = {}; List months = []; StreamSubscription>? messageSub; + final Map> _galleryItemsLastYears = {}; + @override void initState() { super.initState(); @@ -46,6 +47,9 @@ class MemoriesViewState extends State { months = []; var lastMonth = ''; galleryItems = []; + + final now = DateTime.now(); + for (final mediaFile in mediaFiles) { final mediaService = MediaFileService(mediaFile); if (!mediaService.imagePreviewAvailable) continue; @@ -54,12 +58,21 @@ class MemoriesViewState extends State { await mediaService.createThumbnail(); } } - galleryItems.add( - MemoryItem( - mediaService: mediaService, - messages: [], - ), + final item = MemoryItem( + mediaService: mediaService, + messages: [], ); + galleryItems.add(item); + if (mediaFile.createdAt.month == now.month && + mediaFile.createdAt.day == now.day) { + final diff = now.year - mediaFile.createdAt.year; + if (diff > 0) { + if (!_galleryItemsLastYears.containsKey(diff)) { + _galleryItemsLastYears[diff] = []; + } + _galleryItemsLastYears[diff]!.add(item); + } + } } galleryItems.sort( (a, b) => b.mediaService.mediaFile.createdAt.compareTo( @@ -94,8 +107,83 @@ class MemoriesViewState extends State { ), ) : ListView.builder( - itemCount: months.length * 2, + itemCount: (months.length * 2) + + (_galleryItemsLastYears.isEmpty ? 0 : 1), itemBuilder: (context, mIndex) { + if (_galleryItemsLastYears.isNotEmpty && mIndex == 0) { + return SizedBox( + height: 140, + width: MediaQuery.sizeOf(context).width, + child: ListView( + scrollDirection: Axis.horizontal, + children: _galleryItemsLastYears.entries.map( + (item) { + var text = context.lang.memoriesAYearAgo; + if (item.key > 1) { + text = context.lang.memoriesXYearsAgo(item.key); + } + return GestureDetector( + onTap: () async { + await open(context, item.value, 0); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: const [ + BoxShadow( + spreadRadius: -12, + blurRadius: 12, + ), + ], + ), + clipBehavior: Clip.hardEdge, + height: 150, + width: 120, + child: Stack( + children: [ + Positioned.fill( + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.file( + item.value.first.mediaService + .storedPath, + fit: BoxFit.cover, + ), + ), + ), + Positioned( + bottom: 10, + left: 0, + right: 0, + child: Text( + text, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 20, + shadows: [ + Shadow( + color: + Color.fromARGB(122, 0, 0, 0), + blurRadius: 5, + ), + ], + ), + ), + ), + ], + ), + ), + ); + }, + ).toList(), + ), + ); + } + if (_galleryItemsLastYears.isNotEmpty) { + mIndex -= 1; + } if (mIndex.isEven) { return Padding( padding: const EdgeInsets.all(8), @@ -117,7 +205,7 @@ class MemoriesViewState extends State { return MemoriesItemThumbnail( galleryItem: galleryItems[gaIndex], onTap: () async { - await open(context, gaIndex); + await open(context, galleryItems, gaIndex); }, ); }, @@ -128,7 +216,8 @@ class MemoriesViewState extends State { ); } - Future open(BuildContext context, int index) async { + Future open( + BuildContext context, List galleryItems, int index) async { await Navigator.push( context, PageRouteBuilder( @@ -136,13 +225,7 @@ class MemoriesViewState extends State { pageBuilder: (context, a1, a2) => MemoriesPhotoSliderView( galleryItems: galleryItems, initialIndex: index, - scrollDirection: verticalGallery ? Axis.vertical : Axis.horizontal, ), - // transitionsBuilder: (context, animation, secondaryAnimation, child) { - // return child; - // }, - // transitionDuration: Duration.zero, - // reverseTransitionDuration: Duration.zero, ), ) as bool?; if (mounted) setState(() {});