diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index e882159ed..3380d8a8f 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -8307,6 +8307,16 @@ class S { args: [], ); } + + /// `Memories` + String get memories { + return Intl.message( + 'Memories', + name: 'memories', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index 6a71af50f..241fdf15e 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -10,5 +10,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index f64a91832..a0445b7d0 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1178,5 +1178,6 @@ "selectALocationFirst": "Wähle zuerst einen Standort", "changeLocationOfSelectedItems": "Standort der gewählten Elemente ändern?", "editsToLocationWillOnlyBeSeenWithinEnte": "Änderungen des Standorts werden nur in ente sichtbar sein", - "cleanUncategorized": "Unkategorisiert leeren" + "cleanUncategorized": "Unkategorisiert leeren", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index ae31c07d6..cef7ac405 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1187,5 +1187,6 @@ "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", - "cleanUncategorized": "Clean Uncategorized" -} + "cleanUncategorized": "Clean Uncategorized", + "memories": "Memories" +} \ No newline at end of file diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 0720f8d3e..41522054b 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -973,5 +973,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index fe9c4c64c..ddfdb7569 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1154,5 +1154,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 7df3892f5..1a6735bba 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1116,5 +1116,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_ko.arb b/lib/l10n/intl_ko.arb index 6a71af50f..241fdf15e 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -10,5 +10,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index bb8404dc4..009821332 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1163,5 +1163,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_no.arb b/lib/l10n/intl_no.arb index d55787f17..58981e01f 100644 --- a/lib/l10n/intl_no.arb +++ b/lib/l10n/intl_no.arb @@ -24,5 +24,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 7dfb7abc1..16d20a38f 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -111,5 +111,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index e0232c58e..e403970cc 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -277,5 +277,6 @@ "selectALocation": "Select a location", "selectALocationFirst": "Select a location first", "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente" + "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 72c9e991d..a16a858d4 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1186,5 +1186,6 @@ "selectALocationFirst": "首先选择一个位置", "changeLocationOfSelectedItems": "确定要更改所选项目的位置吗?", "editsToLocationWillOnlyBeSeenWithinEnte": "对位置的编辑只能在 Ente 内看到", - "cleanUncategorized": "清除未分类的" + "cleanUncategorized": "清除未分类的", + "memories": "Memories" } \ No newline at end of file diff --git a/lib/ui/home/memories/memories_widget.dart b/lib/ui/home/memories/memories_widget.dart index 72f6add5b..6d1c9aa65 100644 --- a/lib/ui/home/memories/memories_widget.dart +++ b/lib/ui/home/memories/memories_widget.dart @@ -3,9 +3,11 @@ import "dart:async"; import 'package:flutter/material.dart'; import "package:photos/core/event_bus.dart"; import "package:photos/events/memories_setting_changed.dart"; +import "package:photos/generated/l10n.dart"; import 'package:photos/models/memory.dart'; import 'package:photos/services/memories_service.dart'; -import "package:photos/ui/home/memories/memory_cover_widget.dart"; +import "package:photos/theme/ente_theme.dart"; +import 'package:photos/ui/home/memories/memory_cover_widget.dart'; class MemoriesWidget extends StatefulWidget { const MemoriesWidget({Key? key}) : super(key: key); @@ -15,6 +17,8 @@ class MemoriesWidget extends StatefulWidget { } class _MemoriesWidgetState extends State { + final double _widthOfItem = 85; + late ScrollController _controller; late StreamSubscription _subscription; @override @@ -25,11 +29,13 @@ class _MemoriesWidgetState extends State { setState(() {}); } }); + _controller = ScrollController(); } @override void dispose() { _subscription.cancel(); + _controller.dispose(); super.dispose(); } @@ -47,8 +53,34 @@ class _MemoriesWidgetState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Stack( + alignment: Alignment.centerRight, + children: [ + const RotationTransition( + turns: AlwaysStoppedAnimation(20 / 360), + child: Icon( + Icons.favorite_rounded, + color: Color.fromRGBO(0, 179, 60, 0.3), + size: 32, + ), + ), + Padding( + padding: const EdgeInsets.only(right: 16), + child: Text( + S.of(context).memories, + style: getEnteTextTheme(context).body, + ), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), _buildMemories(snapshot.data!), - const Divider(), + const SizedBox(height: 10), ], ); } @@ -58,16 +90,22 @@ class _MemoriesWidgetState extends State { Widget _buildMemories(List memories) { final collatedMemories = _collateMemories(memories); - final List memoryWidgets = []; - for (final memories in collatedMemories) { - memoryWidgets.add(MemoryCovertWidget(memories: memories)); - } - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - physics: const BouncingScrollPhysics(), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: memoryWidgets, + + return SizedBox( + height: MemoryCoverWidget.height, + child: ListView.builder( + physics: const BouncingScrollPhysics(), + scrollDirection: Axis.horizontal, + controller: _controller, + itemCount: collatedMemories.length, + itemBuilder: (context, itemIndex) { + final offsetOfItem = _widthOfItem * itemIndex; + return MemoryCoverWidget( + memories: collatedMemories[itemIndex], + controller: _controller, + offsetOfItem: offsetOfItem, + ); + }, ), ); } @@ -81,6 +119,7 @@ class _MemoriesWidgetState extends State { final List collatedYearlyMemories = []; collatedYearlyMemories.addAll(yearlyMemories); collatedMemories.add(collatedYearlyMemories); + yearlyMemories.clear(); } yearlyMemories.add(memories[index]); diff --git a/lib/ui/home/memories/memory_cover_widget.dart b/lib/ui/home/memories/memory_cover_widget.dart index 5218ba5ff..76e9a929a 100644 --- a/lib/ui/home/memories/memory_cover_widget.dart +++ b/lib/ui/home/memories/memory_cover_widget.dart @@ -2,23 +2,30 @@ import "package:flutter/material.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/models/memory.dart"; import "package:photos/theme/ente_theme.dart"; -import 'package:photos/ui/home/memories/full_screen_memory.dart'; +import "package:photos/ui/home/memories/full_screen_memory.dart"; import "package:photos/ui/viewer/file/thumbnail_widget.dart"; import "package:photos/utils/navigation_util.dart"; -class MemoryCovertWidget extends StatefulWidget { - const MemoryCovertWidget({ - Key? key, - required this.memories, - }) : super(key: key); - +class MemoryCoverWidget extends StatefulWidget { final List memories; + final ScrollController controller; + final double offsetOfItem; + static const centerStrokeWidth = 1.0; + static const width = 85.0; + static const height = 125.0; + + const MemoryCoverWidget({ + required this.memories, + required this.controller, + required this.offsetOfItem, + super.key, + }); @override - State createState() => _MemoryCovertWidgetState(); + State createState() => _MemoryCoverWidgetState(); } -class _MemoryCovertWidgetState extends State { +class _MemoryCoverWidgetState extends State { @override Widget build(BuildContext context) { //memories will be empty if all memories are deleted and setState is called @@ -26,75 +33,186 @@ class _MemoryCovertWidgetState extends State { if (widget.memories.isEmpty) { return const SizedBox.shrink(); } + + final widthOfScreen = MediaQuery.sizeOf(context).width; final index = _getNextMemoryIndex(); final title = _getTitle(widget.memories[index]); - return GestureDetector( - onTap: () async { - await routeToPage( - context, - FullScreenMemoryDataUpdater( - initialIndex: index, - memories: widget.memories, - child: FullScreenMemory(title, index), - ), - forceCustomPageRoute: true, - ); - setState(() {}); - }, - child: Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Column( + final memory = widget.memories[index]; + final isSeen = memory.isSeen(); + final currentTheme = MediaQuery.platformBrightnessOf(context); + + return AnimatedBuilder( + animation: widget.controller, + builder: (context, child) { + final diff = (widget.controller.offset - widget.offsetOfItem) + + widthOfScreen / 7; + final scale = 1 - (diff / widthOfScreen).abs() / 3; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 2.5), + //Adding this row is a workaround for making height of memory cover + //render as [MemoryCoverWidgetNew.height] * scale. Without this, height of rendered memory + //cover will be [MemoryCoverWidgetNew.height]. + child: GestureDetector( + onTap: () async { + await routeToPage( + context, + FullScreenMemoryDataUpdater( + initialIndex: index, + memories: widget.memories, + child: FullScreenMemory(title, index), + ), + forceCustomPageRoute: true, + ); + setState(() {}); + }, + child: Row( children: [ - _buildMemoryItem(context, index), - const Padding(padding: EdgeInsets.all(4)), - Hero( - tag: title, - child: Material( - type: MaterialType.transparency, - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 84), - child: Text( - title, - style: getEnteTextTheme(context).mini, - textAlign: TextAlign.center, + Container( + height: MemoryCoverWidget.height * scale, + width: MemoryCoverWidget.width * scale, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: isSeen + ? currentTheme == Brightness.dark + ? const Color.fromRGBO(104, 104, 104, 0.32) + : Colors.transparent + : const Color.fromRGBO(1, 222, 77, 0.11), + spreadRadius: MemoryCoverWidget.centerStrokeWidth / 2, + blurRadius: 0, ), + const BoxShadow( + color: Color.fromRGBO(0, 0, 0, 0.13), + blurRadius: 3, + offset: Offset(1, 1), + ), + ], + borderRadius: BorderRadius.circular(5), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(5), + child: Stack( + fit: StackFit.expand, + alignment: Alignment.bottomCenter, + children: [ + child!, + Container( + decoration: BoxDecoration( + border: Border.all( + color: isSeen + ? currentTheme == Brightness.dark + ? const Color.fromRGBO( + 104, + 104, + 104, + 0.32, + ) + : Colors.transparent + : const Color.fromRGBO(1, 222, 77, 0.11), + width: MemoryCoverWidget.centerStrokeWidth / 2, + ), + borderRadius: BorderRadius.circular(5), + ), + ), + Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.black.withOpacity(0.5), + Colors.transparent, + ], + stops: const [0, 0.85], + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + ), + ), + ), + isSeen + ? const SizedBox.shrink() + : Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + stops: [0, 0.35, 0.5], + colors: [ + Color.fromARGB(71, 1, 222, 78), + Color(0x1901DE4D), + Color(0x0001DE4D), + ], + transform: GradientRotation(-1.2), + ), + ), + ), + isSeen + ? const SizedBox.shrink() + : Stack( + fit: StackFit.expand, + alignment: Alignment.bottomCenter, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Transform.scale( + scale: scale, + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + stops: [0, 0.5, 1], + colors: [ + Colors.transparent, + Color(0xFF01DE4D), + Colors.transparent, + ], + ), + ), + height: 1 * scale, + width: 68 * scale, + ), + ), + ], + ), + ], + ), + Positioned( + bottom: 8 * scale, + child: Transform.scale( + scale: scale, + child: SizedBox( + width: MemoryCoverWidget.width, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + ), + child: Hero( + tag: title, + child: Text( + title, + style: getEnteTextTheme(context) + .miniBold + .copyWith( + color: Colors.white, + ), + ), + ), + ), + ), + ), + ), + ], ), ), ), ], ), ), - ], - ), - ); - } - - Container _buildMemoryItem(BuildContext context, int index) { - final colorScheme = getEnteColorScheme(context); - final memory = widget.memories[index]; - final isSeen = memory.isSeen(); - return Container( - decoration: BoxDecoration( - border: Border.all( - color: isSeen ? colorScheme.strokeFaint : colorScheme.primary500, - width: 2, - ), - borderRadius: BorderRadius.circular(40), - ), - child: ClipOval( - child: SizedBox( - width: 56, - height: 56, - child: Hero( - tag: "memories" + memory.file.tag, - child: ThumbnailWidget( - memory.file, - shouldShowSyncStatus: false, - key: Key("memories" + memory.file.tag), - ), - ), + ); + }, + child: Hero( + tag: "memories" + memory.file.tag, + child: ThumbnailWidget( + memory.file, + shouldShowArchiveStatus: false, + key: Key("memories" + memory.file.tag), ), ), ); diff --git a/pubspec.yaml b/pubspec.yaml index e74135aa7..3f6f66a63 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.35+555 +version: 0.8.36+556 environment: sdk: ">=3.0.0 <4.0.0"