mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(mobile): memories (#2988)
* Add page view * Nice page view * refactor file structure * Added card * invalidating data * transition * styling * correct styleing * refactor * click to navigate * styling * TODO * clean up * clean up * pr feedback * pr feedback * better loading indicator
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-Black.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-Black.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-Bold.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-Bold.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-ExtraBold.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-ExtraBold.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-Medium.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-Medium.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-SemiBold.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								mobile/fonts/WorkSans-SemiBold.ttf
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -14,6 +14,7 @@ import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/home_page_app_bar.dart'; | ||||
| import 'package:immich_mobile/modules/memories/ui/memory_lane.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/models/album.dart'; | ||||
| @@ -310,6 +311,7 @@ class HomePage extends HookConsumerWidget { | ||||
|                           listener: selectionListener, | ||||
|                           selectionActive: selectionEnabledHook.value, | ||||
|                           onRefresh: refreshAssets, | ||||
|                           topWidget: const MemoryLane(), | ||||
|                         ), | ||||
|                   error: (error, _) => Center(child: Text(error.toString())), | ||||
|                   loading: buildLoadingIndicator, | ||||
|   | ||||
							
								
								
									
										40
									
								
								mobile/lib/modules/memories/models/memory.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								mobile/lib/modules/memories/models/memory.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| // ignore_for_file: public_member_api_docs, sort_constructors_first | ||||
|  | ||||
| import 'package:collection/collection.dart'; | ||||
|  | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
|  | ||||
| class Memory { | ||||
|   final String title; | ||||
|   final List<Asset> assets; | ||||
|   Memory({ | ||||
|     required this.title, | ||||
|     required this.assets, | ||||
|   }); | ||||
|  | ||||
|   Memory copyWith({ | ||||
|     String? title, | ||||
|     List<Asset>? assets, | ||||
|   }) { | ||||
|     return Memory( | ||||
|       title: title ?? this.title, | ||||
|       assets: assets ?? this.assets, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'Memory(title: $title, assets: $assets)'; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) { | ||||
|     if (identical(this, other)) return true; | ||||
|     final listEquals = const DeepCollectionEquality().equals; | ||||
|  | ||||
|     return other is Memory && | ||||
|         other.title == title && | ||||
|         listEquals(other.assets, assets); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => title.hashCode ^ assets.hashCode; | ||||
| } | ||||
| @@ -0,0 +1,9 @@ | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/memories/models/memory.dart'; | ||||
| import 'package:immich_mobile/modules/memories/services/memory.service.dart'; | ||||
|  | ||||
| final memoryFutureProvider = FutureProvider<List<Memory>?>((ref) async { | ||||
|   final service = ref.watch(memoryServiceProvider); | ||||
|  | ||||
|   return await service.getMemoryLane(); | ||||
| }); | ||||
							
								
								
									
										50
									
								
								mobile/lib/modules/memories/services/memory.service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								mobile/lib/modules/memories/services/memory.service.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/modules/memories/models/memory.dart'; | ||||
| import 'package:immich_mobile/shared/providers/api.provider.dart'; | ||||
| import 'package:immich_mobile/shared/services/api.service.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| final memoryServiceProvider = StateProvider<MemoryService>((ref) { | ||||
|   return MemoryService( | ||||
|     ref.watch(apiServiceProvider), | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| class MemoryService { | ||||
|   final log = Logger("MemoryService"); | ||||
|  | ||||
|   final ApiService _apiService; | ||||
|  | ||||
|   MemoryService(this._apiService); | ||||
|  | ||||
|   Future<List<Memory>?> getMemoryLane() async { | ||||
|     try { | ||||
|       final now = DateTime.now(); | ||||
|       final beginningOfDate = DateTime(now.year, now.month, now.day); | ||||
|       final data = await _apiService.assetApi.getMemoryLane( | ||||
|         beginningOfDate, | ||||
|       ); | ||||
|  | ||||
|       if (data == null) { | ||||
|         return null; | ||||
|       } | ||||
|  | ||||
|       List<Memory> memories = []; | ||||
|       for (final MemoryLaneResponseDto(:title, :assets) in data) { | ||||
|         memories.add( | ||||
|           Memory( | ||||
|             title: title, | ||||
|             assets: assets.map((a) => Asset.remote(a)).toList(), | ||||
|           ), | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       return memories.isNotEmpty ? memories : null; | ||||
|     } catch (error, stack) { | ||||
|       log.severe("Cannot get memories ${error.toString()}", error, stack); | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										121
									
								
								mobile/lib/modules/memories/ui/memory_card.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								mobile/lib/modules/memories/ui/memory_card.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/shared/models/store.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_image.dart'; | ||||
| import 'package:immich_mobile/utils/image_url_builder.dart'; | ||||
| import 'package:openapi/api.dart'; | ||||
|  | ||||
| class MemoryCard extends HookConsumerWidget { | ||||
|   final Asset asset; | ||||
|   final void Function() onTap; | ||||
|   final void Function() onClose; | ||||
|   final String title; | ||||
|   final String? rightCornerText; | ||||
|   final bool showTitle; | ||||
|  | ||||
|   const MemoryCard({ | ||||
|     required this.asset, | ||||
|     required this.onTap, | ||||
|     required this.onClose, | ||||
|     required this.title, | ||||
|     required this.showTitle, | ||||
|     this.rightCornerText, | ||||
|     super.key, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}'; | ||||
|  | ||||
|     buildTitle() { | ||||
|       return Text( | ||||
|         title, | ||||
|         style: const TextStyle( | ||||
|           color: Colors.white, | ||||
|           fontWeight: FontWeight.bold, | ||||
|           fontSize: 24.0, | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return Card( | ||||
|       color: Colors.black, | ||||
|       shape: RoundedRectangleBorder( | ||||
|         borderRadius: BorderRadius.circular(25.0), | ||||
|         side: const BorderSide( | ||||
|           color: Colors.black, | ||||
|           width: 1.0, | ||||
|         ), | ||||
|       ), | ||||
|       clipBehavior: Clip.hardEdge, | ||||
|       child: Stack( | ||||
|         children: [ | ||||
|           Container( | ||||
|             decoration: BoxDecoration( | ||||
|               image: DecorationImage( | ||||
|                 image: CachedNetworkImageProvider( | ||||
|                   getThumbnailUrl( | ||||
|                     asset, | ||||
|                   ), | ||||
|                   cacheKey: getThumbnailCacheKey( | ||||
|                     asset, | ||||
|                   ), | ||||
|                   headers: {"Authorization": authToken}, | ||||
|                 ), | ||||
|                 fit: BoxFit.cover, | ||||
|               ), | ||||
|             ), | ||||
|             child: BackdropFilter( | ||||
|               filter: ImageFilter.blur(sigmaX: 60, sigmaY: 60), | ||||
|               child: Container( | ||||
|                 decoration: | ||||
|                     BoxDecoration(color: Colors.black.withOpacity(0.25)), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           GestureDetector( | ||||
|             onTap: onTap, | ||||
|             child: ImmichImage( | ||||
|               asset, | ||||
|               fit: BoxFit.fitWidth, | ||||
|               height: double.infinity, | ||||
|               width: double.infinity, | ||||
|               type: ThumbnailFormat.JPEG, | ||||
|             ), | ||||
|           ), | ||||
|           Positioned( | ||||
|             top: 2.0, | ||||
|             left: 2.0, | ||||
|             child: IconButton( | ||||
|               onPressed: onClose, | ||||
|               icon: const Icon(Icons.close_rounded), | ||||
|               color: Colors.grey[400], | ||||
|             ), | ||||
|           ), | ||||
|           Positioned( | ||||
|             right: 18.0, | ||||
|             top: 18.0, | ||||
|             child: Text( | ||||
|               rightCornerText ?? "", | ||||
|               style: TextStyle( | ||||
|                 color: Colors.grey[200], | ||||
|                 fontSize: 12.0, | ||||
|                 fontWeight: FontWeight.bold, | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|           if (showTitle) | ||||
|             Positioned( | ||||
|               left: 18.0, | ||||
|               bottom: 18.0, | ||||
|               child: buildTitle(), | ||||
|             ) | ||||
|         ], | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										89
									
								
								mobile/lib/modules/memories/ui/memory_lane.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								mobile/lib/modules/memories/ui/memory_lane.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/memories/providers/memory.provider.dart'; | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_image.dart'; | ||||
|  | ||||
| class MemoryLane extends HookConsumerWidget { | ||||
|   const MemoryLane({super.key}); | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final memoryLaneFutureProvider = ref.watch(memoryFutureProvider); | ||||
|  | ||||
|     final memoryLane = memoryLaneFutureProvider | ||||
|         .whenData( | ||||
|           (memories) => memories != null | ||||
|               ? SizedBox( | ||||
|                   height: 200, | ||||
|                   child: ListView.builder( | ||||
|                     scrollDirection: Axis.horizontal, | ||||
|                     shrinkWrap: true, | ||||
|                     itemCount: memories.length, | ||||
|                     itemBuilder: (context, index) { | ||||
|                       final memory = memories[index]; | ||||
|  | ||||
|                       return Padding( | ||||
|                         padding: const EdgeInsets.only(right: 8.0, bottom: 8), | ||||
|                         child: GestureDetector( | ||||
|                           onTap: () { | ||||
|                             AutoRouter.of(context).push( | ||||
|                               VerticalRouteView( | ||||
|                                 memories: memories, | ||||
|                                 memoryIndex: index, | ||||
|                               ), | ||||
|                             ); | ||||
|                           }, | ||||
|                           child: Stack( | ||||
|                             children: [ | ||||
|                               Card( | ||||
|                                 elevation: 3, | ||||
|                                 shape: RoundedRectangleBorder( | ||||
|                                   borderRadius: BorderRadius.circular(13.0), | ||||
|                                 ), | ||||
|                                 clipBehavior: Clip.hardEdge, | ||||
|                                 child: ColorFiltered( | ||||
|                                   colorFilter: ColorFilter.mode( | ||||
|                                     Colors.black.withOpacity(0.1), | ||||
|                                     BlendMode.darken, | ||||
|                                   ), | ||||
|                                   child: ImmichImage( | ||||
|                                     memory.assets[0], | ||||
|                                     fit: BoxFit.cover, | ||||
|                                     width: 130, | ||||
|                                     height: 200, | ||||
|                                     useGrayBoxPlaceholder: true, | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               ), | ||||
|                               Positioned( | ||||
|                                 bottom: 16, | ||||
|                                 left: 16, | ||||
|                                 child: ConstrainedBox( | ||||
|                                   constraints: const BoxConstraints( | ||||
|                                     maxWidth: 114, | ||||
|                                   ), | ||||
|                                   child: Text( | ||||
|                                     memory.title, | ||||
|                                     style: const TextStyle( | ||||
|                                       fontWeight: FontWeight.w500, | ||||
|                                       color: Colors.white, | ||||
|                                       fontSize: 14, | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ), | ||||
|                               ), | ||||
|                             ], | ||||
|                           ), | ||||
|                         ), | ||||
|                       ); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ) | ||||
|               : const SizedBox(), | ||||
|         ) | ||||
|         .value; | ||||
|  | ||||
|     return memoryLane ?? const SizedBox(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										140
									
								
								mobile/lib/modules/memories/views/memory_page.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								mobile/lib/modules/memories/views/memory_page.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/memories/models/memory.dart'; | ||||
| import 'package:immich_mobile/modules/memories/ui/memory_card.dart'; | ||||
| import 'package:intl/intl.dart'; | ||||
|  | ||||
| class MemoryPage extends HookConsumerWidget { | ||||
|   final List<Memory> memories; | ||||
|   final int memoryIndex; | ||||
|  | ||||
|   const MemoryPage({ | ||||
|     required this.memories, | ||||
|     required this.memoryIndex, | ||||
|     super.key, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final memoryPageController = usePageController(initialPage: memoryIndex); | ||||
|     final memoryAssetPageController = usePageController(); | ||||
|     final currentMemory = useState(memories[memoryIndex]); | ||||
|     final currentAssetPage = useState(0); | ||||
|     final assetProgress = useState( | ||||
|       "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}", | ||||
|     ); | ||||
|     const bgColor = Colors.black; | ||||
|  | ||||
|     toNextMemory() { | ||||
|       memoryPageController.nextPage( | ||||
|         duration: const Duration(milliseconds: 500), | ||||
|         curve: Curves.easeIn, | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     toNextAsset(int currentAssetIndex) { | ||||
|       (currentAssetIndex + 1 < currentMemory.value.assets.length) | ||||
|           ? memoryAssetPageController.jumpToPage( | ||||
|               (currentAssetIndex + 1), | ||||
|             ) | ||||
|           : toNextMemory(); | ||||
|     } | ||||
|  | ||||
|     updateProgressText() { | ||||
|       assetProgress.value = | ||||
|           "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}"; | ||||
|     } | ||||
|  | ||||
|     onMemoryChanged(int otherIndex) { | ||||
|       HapticFeedback.mediumImpact(); | ||||
|       currentMemory.value = memories[otherIndex]; | ||||
|       currentAssetPage.value = 0; | ||||
|       updateProgressText(); | ||||
|     } | ||||
|  | ||||
|     onAssetChanged(int otherIndex) { | ||||
|       HapticFeedback.selectionClick(); | ||||
|  | ||||
|       currentAssetPage.value = otherIndex; | ||||
|       updateProgressText(); | ||||
|     } | ||||
|  | ||||
|     buildBottomInfo() { | ||||
|       return Padding( | ||||
|         padding: const EdgeInsets.all(16.0), | ||||
|         child: Row( | ||||
|           children: [ | ||||
|             Column( | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: [ | ||||
|                 Text( | ||||
|                   currentMemory.value.title, | ||||
|                   style: TextStyle( | ||||
|                     color: Colors.grey[400], | ||||
|                     fontSize: 11.0, | ||||
|                     fontWeight: FontWeight.w600, | ||||
|                   ), | ||||
|                 ), | ||||
|                 Text( | ||||
|                   DateFormat.yMMMMd().format( | ||||
|                     currentMemory.value.assets[0].fileCreatedAt, | ||||
|                   ), | ||||
|                   style: const TextStyle( | ||||
|                     color: Colors.white, | ||||
|                     fontSize: 14.0, | ||||
|                     fontWeight: FontWeight.w500, | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|       backgroundColor: bgColor, | ||||
|       body: SafeArea( | ||||
|         child: PageView.builder( | ||||
|           scrollDirection: Axis.vertical, | ||||
|           controller: memoryPageController, | ||||
|           onPageChanged: onMemoryChanged, | ||||
|           itemCount: memories.length, | ||||
|           itemBuilder: (context, mIndex) { | ||||
|             // Build horizontal page | ||||
|             return Column( | ||||
|               children: [ | ||||
|                 Expanded( | ||||
|                   child: PageView.builder( | ||||
|                     controller: memoryAssetPageController, | ||||
|                     onPageChanged: onAssetChanged, | ||||
|                     scrollDirection: Axis.horizontal, | ||||
|                     itemCount: memories[mIndex].assets.length, | ||||
|                     itemBuilder: (context, index) { | ||||
|                       final asset = memories[mIndex].assets[index]; | ||||
|                       return Container( | ||||
|                         color: Colors.black, | ||||
|                         child: MemoryCard( | ||||
|                           asset: asset, | ||||
|                           onTap: () => toNextAsset(index), | ||||
|                           onClose: () => AutoRouter.of(context).pop(), | ||||
|                           rightCornerText: assetProgress.value, | ||||
|                           title: memories[mIndex].title, | ||||
|                           showTitle: index == 0, | ||||
|                         ), | ||||
|                       ); | ||||
|                     }, | ||||
|                   ), | ||||
|                 ), | ||||
|                 buildBottomInfo(), | ||||
|               ], | ||||
|             ); | ||||
|           }, | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -6,6 +6,8 @@ import 'package:immich_mobile/modules/album/views/album_viewer_page.dart'; | ||||
| import 'package:immich_mobile/modules/album/views/asset_selection_page.dart'; | ||||
| import 'package:immich_mobile/modules/album/views/create_album_page.dart'; | ||||
| import 'package:immich_mobile/modules/album/views/library_page.dart'; | ||||
| import 'package:immich_mobile/modules/memories/models/memory.dart'; | ||||
| import 'package:immich_mobile/modules/memories/views/memory_page.dart'; | ||||
| import 'package:immich_mobile/modules/partner/views/partner_detail_page.dart'; | ||||
| import 'package:immich_mobile/modules/partner/views/partner_page.dart'; | ||||
| import 'package:immich_mobile/modules/album/views/select_additional_user_for_sharing_page.dart'; | ||||
| @@ -151,6 +153,7 @@ part 'router.gr.dart'; | ||||
|       ], | ||||
|     ), | ||||
|     AutoRoute(page: AllPeoplePage, guards: [AuthGuard, DuplicateGuard]), | ||||
|     AutoRoute(page: MemoryPage, guards: [AuthGuard, DuplicateGuard]), | ||||
|   ], | ||||
| ) | ||||
| class AppRouter extends _$AppRouter { | ||||
|   | ||||
| @@ -290,6 +290,17 @@ class _$AppRouter extends RootStackRouter { | ||||
|         child: const AllPeoplePage(), | ||||
|       ); | ||||
|     }, | ||||
|     VerticalRouteView.name: (routeData) { | ||||
|       final args = routeData.argsAs<VerticalRouteViewArgs>(); | ||||
|       return MaterialPageX<dynamic>( | ||||
|         routeData: routeData, | ||||
|         child: MemoryPage( | ||||
|           memories: args.memories, | ||||
|           memoryIndex: args.memoryIndex, | ||||
|           key: args.key, | ||||
|         ), | ||||
|       ); | ||||
|     }, | ||||
|     HomeRoute.name: (routeData) { | ||||
|       return MaterialPageX<dynamic>( | ||||
|         routeData: routeData, | ||||
| @@ -589,6 +600,14 @@ class _$AppRouter extends RootStackRouter { | ||||
|             duplicateGuard, | ||||
|           ], | ||||
|         ), | ||||
|         RouteConfig( | ||||
|           VerticalRouteView.name, | ||||
|           path: '/vertical-page-view', | ||||
|           guards: [ | ||||
|             authGuard, | ||||
|             duplicateGuard, | ||||
|           ], | ||||
|         ), | ||||
|       ]; | ||||
| } | ||||
|  | ||||
| @@ -1281,6 +1300,45 @@ class AllPeopleRoute extends PageRouteInfo<void> { | ||||
|   static const String name = 'AllPeopleRoute'; | ||||
| } | ||||
|  | ||||
| /// generated route for | ||||
| /// [MemoryPage] | ||||
| class VerticalRouteView extends PageRouteInfo<VerticalRouteViewArgs> { | ||||
|   VerticalRouteView({ | ||||
|     required List<Memory> memories, | ||||
|     required int memoryIndex, | ||||
|     Key? key, | ||||
|   }) : super( | ||||
|           VerticalRouteView.name, | ||||
|           path: '/vertical-page-view', | ||||
|           args: VerticalRouteViewArgs( | ||||
|             memories: memories, | ||||
|             memoryIndex: memoryIndex, | ||||
|             key: key, | ||||
|           ), | ||||
|         ); | ||||
|  | ||||
|   static const String name = 'VerticalRouteView'; | ||||
| } | ||||
|  | ||||
| class VerticalRouteViewArgs { | ||||
|   const VerticalRouteViewArgs({ | ||||
|     required this.memories, | ||||
|     required this.memoryIndex, | ||||
|     this.key, | ||||
|   }); | ||||
|  | ||||
|   final List<Memory> memories; | ||||
|  | ||||
|   final int memoryIndex; | ||||
|  | ||||
|   final Key? key; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return 'VerticalRouteViewArgs{memories: $memories, memoryIndex: $memoryIndex, key: $key}'; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// generated route for | ||||
| /// [HomePage] | ||||
| class HomeRoute extends PageRouteInfo<void> { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/album/providers/album.provider.dart'; | ||||
| import 'package:immich_mobile/modules/memories/providers/memory.provider.dart'; | ||||
| import 'package:immich_mobile/modules/search/providers/people.provider.dart'; | ||||
|  | ||||
| import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; | ||||
| @@ -43,6 +44,10 @@ class TabNavigationObserver extends AutoRouterObserver { | ||||
|     if (route.name == 'LibraryRoute') { | ||||
|       ref.read(albumProvider.notifier).getAllAlbums(); | ||||
|     } | ||||
|  | ||||
|     if (route.name == 'HomeRoute') { | ||||
|       ref.invalidate(memoryFutureProvider); | ||||
|     } | ||||
|     ref.watch(serverInfoProvider.notifier).getServerVersion(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import 'package:immich_mobile/shared/models/asset.dart'; | ||||
| import 'package:immich_mobile/shared/models/store.dart'; | ||||
| import 'package:immich_mobile/utils/image_url_builder.dart'; | ||||
| import 'package:photo_manager/photo_manager.dart'; | ||||
| import 'package:openapi/api.dart' as api; | ||||
|  | ||||
| /// Renders an Asset using local data if available, else remote data | ||||
| class ImmichImage extends StatelessWidget { | ||||
| @@ -15,6 +16,7 @@ class ImmichImage extends StatelessWidget { | ||||
|     this.height, | ||||
|     this.fit = BoxFit.cover, | ||||
|     this.useGrayBoxPlaceholder = false, | ||||
|     this.type = api.ThumbnailFormat.WEBP, | ||||
|     super.key, | ||||
|   }); | ||||
|   final Asset? asset; | ||||
| @@ -22,6 +24,7 @@ class ImmichImage extends StatelessWidget { | ||||
|   final double? width; | ||||
|   final double? height; | ||||
|   final BoxFit fit; | ||||
|   final api.ThumbnailFormat type; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| @@ -85,7 +88,7 @@ class ImmichImage extends StatelessWidget { | ||||
|       ); | ||||
|     } | ||||
|     final String? token = Store.get(StoreKey.accessToken); | ||||
|     final String thumbnailRequestUrl = getThumbnailUrl(asset); | ||||
|     final String thumbnailRequestUrl = getThumbnailUrl(asset, type: type); | ||||
|     return CachedNetworkImage( | ||||
|       imageUrl: thumbnailRequestUrl, | ||||
|       httpHeaders: {"Authorization": "Bearer $token"}, | ||||
| @@ -105,7 +108,7 @@ class ImmichImage extends StatelessWidget { | ||||
|         } | ||||
|         return Transform.scale( | ||||
|           scale: 0.2, | ||||
|           child: CircularProgressIndicator( | ||||
|           child: CircularProgressIndicator.adaptive( | ||||
|             value: downloadProgress.progress, | ||||
|           ), | ||||
|         ); | ||||
|   | ||||
| @@ -81,6 +81,16 @@ flutter: | ||||
|         - asset: fonts/WorkSans.ttf | ||||
|         - asset: fonts/WorkSans-Italic.ttf | ||||
|           style: italic | ||||
|         # - asset: fonts/WorkSans-Medium.ttf | ||||
|         #   weight: 500 | ||||
|         # - asset: fonts/WorkSans-SemiBold.ttf | ||||
|         #   weight: 600 | ||||
|         # - asset: fonts/WorkSans-Bold.ttf | ||||
|         #   weight: 700 | ||||
|         # - asset: fonts/WorkSans-ExtraBold.ttf | ||||
|         #   weight: 800 | ||||
|         # - asset: fonts/WorkSans-Black.ttf | ||||
|         #   weight: 900 | ||||
|     - family: SnowburstOne | ||||
|       fonts: | ||||
|         - asset: fonts/SnowburstOne.ttf | ||||
|   | ||||
		Reference in New Issue
	
	Block a user