diff --git a/mobile/lib/modules/home/ui/home_page_app_bar.dart b/mobile/lib/modules/home/ui/home_page_app_bar.dart index a9207e57bd..e52ed47be0 100644 --- a/mobile/lib/modules/home/ui/home_page_app_bar.dart +++ b/mobile/lib/modules/home/ui/home_page_app_bar.dart @@ -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( diff --git a/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart b/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart index 7df8c0a00b..fad1055ebb 100644 --- a/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart +++ b/mobile/lib/modules/home/ui/profile_drawer/profile_drawer_header.dart @@ -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}", diff --git a/mobile/lib/shared/ui/transparent_image.dart b/mobile/lib/shared/ui/transparent_image.dart new file mode 100644 index 0000000000..af338463b7 --- /dev/null +++ b/mobile/lib/shared/ui/transparent_image.dart @@ -0,0 +1,68 @@ +import 'dart:typed_data'; + +final Uint8List kTransparentImage = Uint8List.fromList([ + 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, +]); diff --git a/server/package-lock.json b/server/package-lock.json index ee5fa87d97..f9afa13253 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "immich", - "version": "1.45.0", + "version": "1.46.1", "license": "UNLICENSED", "dependencies": { "@nestjs/bull": "^0.6.2",