You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	feat(mobile): Uses profile photo for user avatar drawer (#1738)
* uses profile photo for user avatar drawer
* Added some styling to the profile picture
* made the whole profile photo a gesture detector
* fixed image updating
* invalidates cachednetworkimage when new profile photo is uploaded
* Revert "invalidates cachednetworkimage when new profile photo is uploaded"
This reverts commit 17c83be556.
* Add fadeInImage to loading user profile
---------
Co-authored-by: Alex <alex.tran1502@gmail.com>
			
			
This commit is contained in:
		| @@ -1,6 +1,11 @@ | ||||
| import 'dart:math'; | ||||
|  | ||||
| import 'package:auto_route/auto_route.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:hive/hive.dart'; | ||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||
| import 'package:immich_mobile/constants/hive_box.dart'; | ||||
| import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; | ||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||
|  | ||||
| import 'package:immich_mobile/routing/router.dart'; | ||||
| @@ -8,15 +13,16 @@ import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; | ||||
| import 'package:immich_mobile/shared/models/server_info_state.model.dart'; | ||||
| import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; | ||||
| import 'package:immich_mobile/shared/providers/server_info.provider.dart'; | ||||
| import 'package:immich_mobile/shared/ui/transparent_image.dart'; | ||||
|  | ||||
| class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget { | ||||
|   @override | ||||
|   Size get preferredSize => const Size.fromHeight(kToolbarHeight); | ||||
|  | ||||
|   const HomePageAppBar({ | ||||
|     Key? key, | ||||
|     super.key, | ||||
|     this.onPopBack, | ||||
|   }) : super(key: key); | ||||
|   }); | ||||
|  | ||||
|   final Function? onPopBack; | ||||
|  | ||||
| @@ -26,6 +32,46 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget { | ||||
|     bool isEnableAutoBackup = backupState.backgroundBackup || | ||||
|         ref.watch(authenticationProvider).deviceInfo.isAutoBackup; | ||||
|     final ServerInfoState serverInfoState = ref.watch(serverInfoProvider); | ||||
|     AuthenticationState authState = ref.watch(authenticationProvider); | ||||
|  | ||||
|     buildProfilePhoto() { | ||||
|       if (authState.profileImagePath.isEmpty) { | ||||
|         return IconButton( | ||||
|           splashRadius: 25, | ||||
|           icon: const Icon( | ||||
|             Icons.face_outlined, | ||||
|             size: 30, | ||||
|           ), | ||||
|           onPressed: () { | ||||
|             Scaffold.of(context).openDrawer(); | ||||
|           }, | ||||
|         ); | ||||
|       } else { | ||||
|         String endpoint = Hive.box(userInfoBox).get(serverEndpointKey); | ||||
|         var dummy = Random().nextInt(1024); | ||||
|         return InkWell( | ||||
|           onTap: () { | ||||
|             Scaffold.of(context).openDrawer(); | ||||
|           }, | ||||
|           child: CircleAvatar( | ||||
|             backgroundColor: Theme.of(context).primaryColor, | ||||
|             radius: 18, | ||||
|             child: ClipRRect( | ||||
|               borderRadius: BorderRadius.circular(50), | ||||
|               child: FadeInImage.memoryNetwork( | ||||
|                 fit: BoxFit.cover, | ||||
|                 placeholder: kTransparentImage, | ||||
|                 width: 33, | ||||
|                 height: 33, | ||||
|                 image: | ||||
|                     '$endpoint/user/profile-image/${authState.userId}?d=${dummy++}', | ||||
|                 fadeInDuration: const Duration(milliseconds: 200), | ||||
|               ), | ||||
|             ), | ||||
|           ), | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return AppBar( | ||||
|       backgroundColor: Theme.of(context).appBarTheme.backgroundColor, | ||||
| @@ -38,18 +84,8 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget { | ||||
|         builder: (BuildContext context) { | ||||
|           return Stack( | ||||
|             children: [ | ||||
|               Positioned( | ||||
|                 top: 5, | ||||
|                 child: IconButton( | ||||
|                   splashRadius: 25, | ||||
|                   icon: const Icon( | ||||
|                     Icons.face_outlined, | ||||
|                     size: 30, | ||||
|                   ), | ||||
|                   onPressed: () { | ||||
|                     Scaffold.of(context).openDrawer(); | ||||
|                   }, | ||||
|                 ), | ||||
|               Center( | ||||
|                 child: buildProfilePhoto(), | ||||
|               ), | ||||
|               if (serverInfoState.isVersionMismatch) | ||||
|                 Positioned( | ||||
|   | ||||
| @@ -10,6 +10,7 @@ import 'package:immich_mobile/modules/home/providers/upload_profile_image.provid | ||||
| import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; | ||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||
| import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; | ||||
| import 'package:immich_mobile/shared/ui/transparent_image.dart'; | ||||
|  | ||||
| class ProfileDrawerHeader extends HookConsumerWidget { | ||||
|   const ProfileDrawerHeader({ | ||||
| @@ -26,6 +27,23 @@ class ProfileDrawerHeader extends HookConsumerWidget { | ||||
|     final isDarkMode = Theme.of(context).brightness == Brightness.dark; | ||||
|  | ||||
|     buildUserProfileImage() { | ||||
|       var userImage = CircleAvatar( | ||||
|         backgroundColor: Theme.of(context).primaryColor, | ||||
|         radius: 35, | ||||
|         child: ClipRRect( | ||||
|           borderRadius: BorderRadius.circular(50), | ||||
|           child: FadeInImage.memoryNetwork( | ||||
|             fit: BoxFit.cover, | ||||
|             placeholder: kTransparentImage, | ||||
|             width: 66, | ||||
|             height: 66, | ||||
|             image: | ||||
|                 '$endpoint/user/profile-image/${authState.userId}?d=${dummy++}', | ||||
|             fadeInDuration: const Duration(milliseconds: 200), | ||||
|           ), | ||||
|         ), | ||||
|       ); | ||||
|  | ||||
|       if (authState.profileImagePath.isEmpty) { | ||||
|         return const CircleAvatar( | ||||
|           radius: 35, | ||||
| @@ -36,16 +54,10 @@ class ProfileDrawerHeader extends HookConsumerWidget { | ||||
|  | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.idle) { | ||||
|         if (authState.profileImagePath.isNotEmpty) { | ||||
|           return CircleAvatar( | ||||
|             radius: 35, | ||||
|             backgroundImage: NetworkImage( | ||||
|               '$endpoint/user/profile-image/${authState.userId}?d=${dummy++}', | ||||
|             ), | ||||
|             backgroundColor: Colors.transparent, | ||||
|           ); | ||||
|           return userImage; | ||||
|         } else { | ||||
|           return const CircleAvatar( | ||||
|             radius: 35, | ||||
|             radius: 33, | ||||
|             backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), | ||||
|             backgroundColor: Colors.transparent, | ||||
|           ); | ||||
| @@ -53,13 +65,7 @@ class ProfileDrawerHeader extends HookConsumerWidget { | ||||
|       } | ||||
|  | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.success) { | ||||
|         return CircleAvatar( | ||||
|           radius: 35, | ||||
|           backgroundImage: NetworkImage( | ||||
|             '$endpoint/user/profile-image/${authState.userId}?d=${dummy++}', | ||||
|           ), | ||||
|           backgroundColor: Colors.transparent, | ||||
|         ); | ||||
|         return userImage; | ||||
|       } | ||||
|  | ||||
|       if (uploadProfileImageStatus == UploadProfileStatus.failure) { | ||||
| @@ -98,7 +104,7 @@ class ProfileDrawerHeader extends HookConsumerWidget { | ||||
|  | ||||
|     useEffect( | ||||
|       () { | ||||
|         buildUserProfileImage(); | ||||
|         // buildUserProfileImage(); | ||||
|         return null; | ||||
|       }, | ||||
|       [], | ||||
| @@ -126,17 +132,17 @@ class ProfileDrawerHeader extends HookConsumerWidget { | ||||
|         mainAxisAlignment: MainAxisAlignment.start, | ||||
|         crossAxisAlignment: CrossAxisAlignment.start, | ||||
|         children: [ | ||||
|           Stack( | ||||
|             clipBehavior: Clip.none, | ||||
|             children: [ | ||||
|               buildUserProfileImage(), | ||||
|               Positioned( | ||||
|                 bottom: 0, | ||||
|                 right: -5, | ||||
|                 child: GestureDetector( | ||||
|                   onTap: pickUserProfileImage, | ||||
|           GestureDetector( | ||||
|             onTap: pickUserProfileImage, | ||||
|             child: Stack( | ||||
|               clipBehavior: Clip.none, | ||||
|               children: [ | ||||
|                 buildUserProfileImage(), | ||||
|                 Positioned( | ||||
|                   bottom: 0, | ||||
|                   right: -5, | ||||
|                   child: Material( | ||||
|                     color: Colors.grey[100], | ||||
|                     color: isDarkMode ? Colors.grey[700] : Colors.grey[100], | ||||
|                     elevation: 3, | ||||
|                     shape: RoundedRectangleBorder( | ||||
|                       borderRadius: BorderRadius.circular(50.0), | ||||
| @@ -151,8 +157,8 @@ class ProfileDrawerHeader extends HookConsumerWidget { | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|               ], | ||||
|             ), | ||||
|           ), | ||||
|           Text( | ||||
|             "${authState.firstName} ${authState.lastName}", | ||||
|   | ||||
							
								
								
									
										68
									
								
								mobile/lib/shared/ui/transparent_image.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								mobile/lib/shared/ui/transparent_image.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| import 'dart:typed_data'; | ||||
|  | ||||
| final Uint8List kTransparentImage = Uint8List.fromList(<int>[ | ||||
|   0x89, | ||||
|   0x50, | ||||
|   0x4E, | ||||
|   0x47, | ||||
|   0x0D, | ||||
|   0x0A, | ||||
|   0x1A, | ||||
|   0x0A, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x0D, | ||||
|   0x49, | ||||
|   0x48, | ||||
|   0x44, | ||||
|   0x52, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x01, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x01, | ||||
|   0x08, | ||||
|   0x06, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x1F, | ||||
|   0x15, | ||||
|   0xC4, | ||||
|   0x89, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x0A, | ||||
|   0x49, | ||||
|   0x44, | ||||
|   0x41, | ||||
|   0x54, | ||||
|   0x78, | ||||
|   0x9C, | ||||
|   0x63, | ||||
|   0x00, | ||||
|   0x01, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x05, | ||||
|   0x00, | ||||
|   0x01, | ||||
|   0x0D, | ||||
|   0x0A, | ||||
|   0x2D, | ||||
|   0xB4, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x00, | ||||
|   0x49, | ||||
|   0x45, | ||||
|   0x4E, | ||||
|   0x44, | ||||
|   0xAE, | ||||
| ]); | ||||
							
								
								
									
										2
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								server/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -6,7 +6,7 @@ | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "immich", | ||||
|       "version": "1.45.0", | ||||
|       "version": "1.46.1", | ||||
|       "license": "UNLICENSED", | ||||
|       "dependencies": { | ||||
|         "@nestjs/bull": "^0.6.2", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user