mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	Implementing scroll bar like Google Photos
This commit is contained in:
		
							
								
								
									
										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 | To start, run | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| docker-compose up ./server | docker-compose -f ./server/docker-compose.yml up | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| To force rebuild node modules after installing new packages | To force rebuild node modules after installing new packages | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| docker-compose up --build -V ./server | docker-compose -f ./server/docker-compose.yml up --build -V | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| # Known Issue | # Known Issue | ||||||
|   | |||||||
| @@ -46,10 +46,6 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> initApp() async { |   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); |     // WidgetsBinding.instance?.addObserver(this); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -75,12 +75,14 @@ class ImmichSliverAppBar extends ConsumerWidget { | |||||||
|                 onPressed: () async { |                 onPressed: () async { | ||||||
|                   var onPop = await AutoRouter.of(context).push(const BackupControllerRoute()); |                   var onPop = await AutoRouter.of(context).push(const BackupControllerRoute()); | ||||||
|  |  | ||||||
|                   // Fetch new image |  | ||||||
|                   if (onPop == true) { |                   if (onPop == true) { | ||||||
|                     // Remove and force getting new widget again |                     // Remove and force getting new widget again if there is not many widget on screen. | ||||||
|                     if (imageGridGroup.isNotEmpty) { |                     // Otherwise do nothing. | ||||||
|  |                     if (imageGridGroup.isNotEmpty && imageGridGroup.length < 20) { | ||||||
|  |                       print("Get more access"); | ||||||
|                       ref.read(assetProvider.notifier).getMoreAsset(); |                       ref.read(assetProvider.notifier).getMoreAsset(); | ||||||
|                     } else { |                     } else if (imageGridGroup.isEmpty) { | ||||||
|  |                       print("get immich asset"); | ||||||
|                       ref.read(assetProvider.notifier).getImmichAssets(); |                       ref.read(assetProvider.notifier).getImmichAssets(); | ||||||
|                     } |                     } | ||||||
|                   } |                   } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter/rendering.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.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/ui/image_grid.dart'; | ||||||
| import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; | import 'package:immich_mobile/modules/home/providers/asset.provider.dart'; | ||||||
| import 'package:immich_mobile/shared/providers/backup.provider.dart'; | import 'package:immich_mobile/shared/providers/backup.provider.dart'; | ||||||
|  | import 'package:visibility_detector/visibility_detector.dart'; | ||||||
| import 'package:intl/intl.dart'; | import 'package:intl/intl.dart'; | ||||||
|  |  | ||||||
| class HomePage extends HookConsumerWidget { | class HomePage extends HookConsumerWidget { | ||||||
| @@ -18,10 +20,9 @@ class HomePage extends HookConsumerWidget { | |||||||
|     final ValueNotifier<bool> _showBackToTopBtn = useState(false); |     final ValueNotifier<bool> _showBackToTopBtn = useState(false); | ||||||
|     ScrollController _scrollController = useScrollController(); |     ScrollController _scrollController = useScrollController(); | ||||||
|     List<ImmichAssetGroupByDate> assetGroup = ref.watch(assetProvider); |     List<ImmichAssetGroupByDate> assetGroup = ref.watch(assetProvider); | ||||||
|     BackUpState _backupState = ref.watch(backupProvider); |  | ||||||
|     List<Widget> imageGridGroup = []; |     List<Widget> imageGridGroup = []; | ||||||
|     List<GlobalKey> monthGroupKey = []; |     List<GlobalKey> monthGroupKey = []; | ||||||
|  |     final monthInView = useState<String>(""); | ||||||
|     _scrollControllerCallback() { |     _scrollControllerCallback() { | ||||||
|       var endOfPage = _scrollController.position.maxScrollExtent; |       var endOfPage = _scrollController.position.maxScrollExtent; | ||||||
|  |  | ||||||
| @@ -34,6 +35,13 @@ class HomePage extends HookConsumerWidget { | |||||||
|       } else { |       } else { | ||||||
|         _showBackToTopBtn.value = false; |         _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(() { |     useEffect(() { | ||||||
| @@ -41,8 +49,63 @@ class HomePage extends HookConsumerWidget { | |||||||
|  |  | ||||||
|       _scrollController.addListener(_scrollControllerCallback); |       _scrollController.addListener(_scrollControllerCallback); | ||||||
|  |  | ||||||
|       return () => _scrollController.removeListener(_scrollControllerCallback); |       return () { | ||||||
|     }, [_scrollController, key]); |         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() { |     Widget _buildBody() { | ||||||
|       if (assetGroup.isNotEmpty) { |       if (assetGroup.isNotEmpty) { | ||||||
| @@ -56,28 +119,9 @@ class HomePage extends HookConsumerWidget { | |||||||
|           int? previousMonth = DateTime.tryParse(lastGroupDate)?.month; |           int? previousMonth = DateTime.tryParse(lastGroupDate)?.month; | ||||||
|  |  | ||||||
|           if ((currentMonth! - previousMonth!) != 0) { |           if ((currentMonth! - previousMonth!) != 0) { | ||||||
|             var myKey = GlobalKey(); |             var monthTitleText = DateFormat('MMMM, y').format(DateTime.parse(dateTitle)); | ||||||
|             monthGroupKey.add(myKey); |  | ||||||
|             // debugPrint("Group Key $myKey"); |  | ||||||
|  |  | ||||||
|             imageGridGroup.add( |             imageGridGroup.add(_buildMonthGroupTitle(monthTitleText, context)); | ||||||
|               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( |           imageGridGroup.add( | ||||||
| @@ -88,39 +132,36 @@ class HomePage extends HookConsumerWidget { | |||||||
|  |  | ||||||
|           lastGroupDate = dateTitle; |           lastGroupDate = dateTitle; | ||||||
|         } |         } | ||||||
|       }  |       } | ||||||
|  |  | ||||||
|        return SafeArea( |       return SafeArea( | ||||||
|           child: CustomScrollView( |         child: Stack(children: [ | ||||||
|  |           RawScrollbar( | ||||||
|  |             minThumbLength: 50, | ||||||
|  |             isAlwaysShown: false, | ||||||
|  |             interactive: true, | ||||||
|             controller: _scrollController, |             controller: _scrollController, | ||||||
|             slivers: [ |             thickness: 50, | ||||||
|               ImmichSliverAppBar(imageGridGroup: imageGridGroup), |             crossAxisMargin: -20, | ||||||
|               ...imageGridGroup, |             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( |     return Scaffold( | ||||||
|       drawer: const ProfileDrawer(), |       drawer: const ProfileDrawer(), | ||||||
|       body: _buildBody(), |       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: _showBackToTopBtn.value | ||||||
|           ? FloatingActionButton.small( |           ? FloatingActionButton.small( | ||||||
|               enableFeedback: true, |               enableFeedback: true, | ||||||
| @@ -134,30 +175,4 @@ class HomePage extends HookConsumerWidget { | |||||||
|           : null, |           : 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) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final usernameController = useTextEditingController(text: 'testuser@email.com'); |     final usernameController = useTextEditingController(text: 'testuser@email.com'); | ||||||
|     final passwordController = useTextEditingController(text: 'password'); |     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( |     return Center( | ||||||
|       child: ConstrainedBox( |       child: ConstrainedBox( | ||||||
|   | |||||||
| @@ -805,6 +805,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.1" |     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: |   watcher: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -25,6 +25,8 @@ dependencies: | |||||||
|   auto_route: ^3.2.2 |   auto_route: ^3.2.2 | ||||||
|   exif: ^3.1.1 |   exif: ^3.1.1 | ||||||
|   transparent_image: ^2.0.0 |   transparent_image: ^2.0.0 | ||||||
|  |   visibility_detector: ^0.2.2 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|     sdk: flutter |     sdk: flutter | ||||||
|   | |||||||
| @@ -63,7 +63,7 @@ export class AssetService { | |||||||
|           lastQueryCreatedAt: query.nextPageKey || new Date().toISOString(), |           lastQueryCreatedAt: query.nextPageKey || new Date().toISOString(), | ||||||
|         }) |         }) | ||||||
|         .orderBy('a."createdAt"::date', 'DESC') |         .orderBy('a."createdAt"::date', 'DESC') | ||||||
|         .take(200) |         .take(10000) | ||||||
|         .getMany(); |         .getMany(); | ||||||
|  |  | ||||||
|       if (assets.length > 0) { |       if (assets.length > 0) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user