mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 02:27:08 -04:00 
			
		
		
		
	Implementing scroll bar like Google Photos
This commit is contained in:
		
							parent
							
								
									bafc32f30a
								
							
						
					
					
						commit
						56c92cd83b
					
				
							
								
								
									
										5
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| run_server_dev: | ||||
| 	docker-compose -f ./server/docker-compose.yml up | ||||
| 
 | ||||
| run_server_update: | ||||
| 	docker-compose -f ./server/docker-compose.yml up --build -V | ||||
| @ -27,13 +27,13 @@ Then populate the value in there. | ||||
| To start, run | ||||
| 
 | ||||
| ```bash | ||||
| docker-compose up ./server | ||||
| docker-compose -f ./server/docker-compose.yml up | ||||
| ``` | ||||
| 
 | ||||
| To force rebuild node modules after installing new packages | ||||
| 
 | ||||
| ```bash | ||||
| docker-compose up --build -V ./server | ||||
| docker-compose -f ./server/docker-compose.yml up --build -V | ||||
| ``` | ||||
| 
 | ||||
| # Known Issue | ||||
|  | ||||
| @ -46,10 +46,6 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv | ||||
|   } | ||||
| 
 | ||||
|   Future<void> initApp() async { | ||||
|     // ! TOBE DELETE | ||||
|     // Simulate Sign In And Register/Get Device ID | ||||
|     // await ref.read(authenticationProvider.notifier).login(); | ||||
|     // ref.read(backupProvider.notifier).getBackupInfo(); | ||||
|     // WidgetsBinding.instance?.addObserver(this); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -75,12 +75,14 @@ class ImmichSliverAppBar extends ConsumerWidget { | ||||
|                 onPressed: () async { | ||||
|                   var onPop = await AutoRouter.of(context).push(const BackupControllerRoute()); | ||||
| 
 | ||||
|                   // Fetch new image | ||||
|                   if (onPop == true) { | ||||
|                     // Remove and force getting new widget again | ||||
|                     if (imageGridGroup.isNotEmpty) { | ||||
|                     // Remove and force getting new widget again if there is not many widget on screen. | ||||
|                     // Otherwise do nothing. | ||||
|                     if (imageGridGroup.isNotEmpty && imageGridGroup.length < 20) { | ||||
|                       print("Get more access"); | ||||
|                       ref.read(assetProvider.notifier).getMoreAsset(); | ||||
|                     } else { | ||||
|                     } else if (imageGridGroup.isEmpty) { | ||||
|                       print("get immich asset"); | ||||
|                       ref.read(assetProvider.notifier).getImmichAssets(); | ||||
|                     } | ||||
|                   } | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/rendering.dart'; | ||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart'; | ||||
| @ -8,6 +9,7 @@ import 'package:immich_mobile/modules/home/models/get_all_asset_respose.model.da | ||||
| import 'package:immich_mobile/modules/home/ui/image_grid.dart'; | ||||
| import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; | ||||
| import 'package:immich_mobile/shared/providers/backup.provider.dart'; | ||||
| import 'package:visibility_detector/visibility_detector.dart'; | ||||
| import 'package:intl/intl.dart'; | ||||
| 
 | ||||
| class HomePage extends HookConsumerWidget { | ||||
| @ -18,10 +20,9 @@ class HomePage extends HookConsumerWidget { | ||||
|     final ValueNotifier<bool> _showBackToTopBtn = useState(false); | ||||
|     ScrollController _scrollController = useScrollController(); | ||||
|     List<ImmichAssetGroupByDate> assetGroup = ref.watch(assetProvider); | ||||
|     BackUpState _backupState = ref.watch(backupProvider); | ||||
|     List<Widget> imageGridGroup = []; | ||||
|     List<GlobalKey> monthGroupKey = []; | ||||
| 
 | ||||
|     final monthInView = useState<String>(""); | ||||
|     _scrollControllerCallback() { | ||||
|       var endOfPage = _scrollController.position.maxScrollExtent; | ||||
| 
 | ||||
| @ -34,6 +35,13 @@ class HomePage extends HookConsumerWidget { | ||||
|       } else { | ||||
|         _showBackToTopBtn.value = false; | ||||
|       } | ||||
| 
 | ||||
|       // Quick Scroll For Jumping to Month | ||||
|       if (_scrollController.position.userScrollDirection == ScrollDirection.forward) { | ||||
|         // Scroll UP | ||||
|       } else if (_scrollController.position.userScrollDirection == ScrollDirection.reverse) { | ||||
|         // SCroll Down | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     useEffect(() { | ||||
| @ -41,8 +49,63 @@ class HomePage extends HookConsumerWidget { | ||||
| 
 | ||||
|       _scrollController.addListener(_scrollControllerCallback); | ||||
| 
 | ||||
|       return () => _scrollController.removeListener(_scrollControllerCallback); | ||||
|     }, [_scrollController, key]); | ||||
|       return () { | ||||
|         debugPrint("Remove scroll listener"); | ||||
|         _scrollController.removeListener(_scrollControllerCallback); | ||||
|       }; | ||||
|     }, []); | ||||
| 
 | ||||
|     SliverToBoxAdapter _buildMonthGroupTitle(String dateTitle, BuildContext context) { | ||||
|       return SliverToBoxAdapter( | ||||
|         child: Padding( | ||||
|           padding: const EdgeInsets.only(left: 10.0, top: 32), | ||||
|           child: Text( | ||||
|             DateFormat('MMMM, y').format( | ||||
|               DateTime.parse(dateTitle), | ||||
|             ), | ||||
|             style: TextStyle( | ||||
|               fontSize: 24, | ||||
|               fontWeight: FontWeight.bold, | ||||
|               color: Theme.of(context).primaryColor, | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     SliverToBoxAdapter _buildDateGroupTitle(String dateTitle) { | ||||
|       var currentYear = DateTime.now().year; | ||||
|       var groupYear = DateTime.parse(dateTitle).year; | ||||
|       var formatDateTemplate = currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy'; | ||||
|       var dateText = DateFormat(formatDateTemplate).format(DateTime.parse(dateTitle)); | ||||
|       var monthText = DateFormat('MMMM, y').format(DateTime.parse(dateTitle)); | ||||
|       return SliverToBoxAdapter( | ||||
|         child: VisibilityDetector( | ||||
|           key: Key(dateText), | ||||
|           onVisibilityChanged: (visibilityInfo) { | ||||
|             monthInView.value = monthText; | ||||
|           }, | ||||
|           child: Padding( | ||||
|             padding: const EdgeInsets.only(top: 24.0, bottom: 24.0, left: 3.0), | ||||
|             child: Row( | ||||
|               children: [ | ||||
|                 Padding( | ||||
|                   padding: const EdgeInsets.only(left: 8.0, bottom: 5.0, top: 5.0), | ||||
|                   child: Text( | ||||
|                     dateText, | ||||
|                     style: const TextStyle( | ||||
|                       fontSize: 14, | ||||
|                       fontWeight: FontWeight.bold, | ||||
|                       color: Colors.black87, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     Widget _buildBody() { | ||||
|       if (assetGroup.isNotEmpty) { | ||||
| @ -56,28 +119,9 @@ class HomePage extends HookConsumerWidget { | ||||
|           int? previousMonth = DateTime.tryParse(lastGroupDate)?.month; | ||||
| 
 | ||||
|           if ((currentMonth! - previousMonth!) != 0) { | ||||
|             var myKey = GlobalKey(); | ||||
|             monthGroupKey.add(myKey); | ||||
|             // debugPrint("Group Key $myKey"); | ||||
|             var monthTitleText = DateFormat('MMMM, y').format(DateTime.parse(dateTitle)); | ||||
| 
 | ||||
|             imageGridGroup.add( | ||||
|               SliverToBoxAdapter( | ||||
|                 key: myKey, | ||||
|                 child: Padding( | ||||
|                   padding: const EdgeInsets.only(left: 10.0, top: 32), | ||||
|                   child: Text( | ||||
|                     DateFormat('MMMM, y').format( | ||||
|                       DateTime.parse(dateTitle), | ||||
|                     ), | ||||
|                     style: TextStyle( | ||||
|                       fontSize: 24, | ||||
|                       fontWeight: FontWeight.bold, | ||||
|                       color: Theme.of(context).primaryColor, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ); | ||||
|             imageGridGroup.add(_buildMonthGroupTitle(monthTitleText, context)); | ||||
|           } | ||||
| 
 | ||||
|           imageGridGroup.add( | ||||
| @ -88,39 +132,36 @@ class HomePage extends HookConsumerWidget { | ||||
| 
 | ||||
|           lastGroupDate = dateTitle; | ||||
|         } | ||||
|       }  | ||||
|       } | ||||
| 
 | ||||
|        return SafeArea( | ||||
|           child: CustomScrollView( | ||||
|       return SafeArea( | ||||
|         child: Stack(children: [ | ||||
|           RawScrollbar( | ||||
|             minThumbLength: 50, | ||||
|             isAlwaysShown: false, | ||||
|             interactive: true, | ||||
|             controller: _scrollController, | ||||
|             slivers: [ | ||||
|               ImmichSliverAppBar(imageGridGroup: imageGridGroup), | ||||
|               ...imageGridGroup, | ||||
|             ], | ||||
|             thickness: 50, | ||||
|             crossAxisMargin: -20, | ||||
|             mainAxisMargin: 70, | ||||
|             timeToFade: const Duration(seconds: 2), | ||||
|             thumbColor: Colors.blueGrey, | ||||
|             radius: const Radius.circular(30), | ||||
|             child: CustomScrollView( | ||||
|               controller: _scrollController, | ||||
|               slivers: [ | ||||
|                 ImmichSliverAppBar(imageGridGroup: imageGridGroup), | ||||
|                 ...imageGridGroup, | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
|         ]), | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return Scaffold( | ||||
|       drawer: const ProfileDrawer(), | ||||
|       body: _buildBody(), | ||||
|       bottomNavigationBar: BottomAppBar( | ||||
|         child: IconButton( | ||||
|           onPressed: () { | ||||
|             if (monthGroupKey.isNotEmpty) { | ||||
|               var targetContext = monthGroupKey.last.currentContext; | ||||
|               if (targetContext != null) { | ||||
|                 Scrollable.ensureVisible( | ||||
|                   targetContext, | ||||
|                   duration: const Duration(milliseconds: 400), | ||||
|                   curve: Curves.easeInOut, | ||||
|                 ); | ||||
|               } | ||||
|             } | ||||
|           }, | ||||
|           icon: const Icon(Icons.ac_unit_outlined), | ||||
|         ), | ||||
|       ), | ||||
|       floatingActionButton: _showBackToTopBtn.value | ||||
|           ? FloatingActionButton.small( | ||||
|               enableFeedback: true, | ||||
| @ -134,30 +175,4 @@ class HomePage extends HookConsumerWidget { | ||||
|           : null, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   SliverToBoxAdapter _buildDateGroupTitle(String dateTitle) { | ||||
|     var currentYear = DateTime.now().year; | ||||
|     var groupYear = DateTime.parse(dateTitle).year; | ||||
|     var formatDateTemplate = currentYear == groupYear ? 'E, MMM dd' : 'E, MMM dd, yyyy'; | ||||
|     return SliverToBoxAdapter( | ||||
|       child: Padding( | ||||
|         padding: const EdgeInsets.only(top: 24.0, bottom: 24.0, left: 3.0), | ||||
|         child: Row( | ||||
|           children: [ | ||||
|             Padding( | ||||
|               padding: const EdgeInsets.only(left: 8.0, bottom: 5.0, top: 5.0), | ||||
|               child: Text( | ||||
|                 DateFormat(formatDateTemplate).format(DateTime.parse(dateTitle)), | ||||
|                 style: const TextStyle( | ||||
|                   fontSize: 14, | ||||
|                   fontWeight: FontWeight.bold, | ||||
|                   color: Colors.black87, | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|           ], | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -12,7 +12,7 @@ class LoginForm extends HookConsumerWidget { | ||||
|   Widget build(BuildContext context, WidgetRef ref) { | ||||
|     final usernameController = useTextEditingController(text: 'testuser@email.com'); | ||||
|     final passwordController = useTextEditingController(text: 'password'); | ||||
|     final serverEndpointController = useTextEditingController(text: 'http://192.168.1.216'); | ||||
|     final serverEndpointController = useTextEditingController(text: 'http://192.168.1.103:3000'); | ||||
| 
 | ||||
|     return Center( | ||||
|       child: ConstrainedBox( | ||||
|  | ||||
| @ -805,6 +805,13 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.1" | ||||
|   visibility_detector: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: visibility_detector | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.2.2" | ||||
|   watcher: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | ||||
| @ -25,6 +25,8 @@ dependencies: | ||||
|   auto_route: ^3.2.2 | ||||
|   exif: ^3.1.1 | ||||
|   transparent_image: ^2.0.0 | ||||
|   visibility_detector: ^0.2.2 | ||||
| 
 | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|     sdk: flutter | ||||
|  | ||||
| @ -63,7 +63,7 @@ export class AssetService { | ||||
|           lastQueryCreatedAt: query.nextPageKey || new Date().toISOString(), | ||||
|         }) | ||||
|         .orderBy('a."createdAt"::date', 'DESC') | ||||
|         .take(200) | ||||
|         .take(10000) | ||||
|         .getMany(); | ||||
| 
 | ||||
|       if (assets.length > 0) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user