mirror of
https://github.com/immich-app/immich.git
synced 2025-01-06 11:56:46 +01: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:
parent
caac3bfc95
commit
12a6a7d95a
4 changed files with 153 additions and 43 deletions
|
@ -1,6 +1,11 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/modules/login/providers/authentication.provider.dart';
|
||||||
|
|
||||||
import 'package:immich_mobile/routing/router.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/shared/models/server_info_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.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/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
||||||
|
|
||||||
class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
|
class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
|
||||||
@override
|
@override
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||||
|
|
||||||
const HomePageAppBar({
|
const HomePageAppBar({
|
||||||
Key? key,
|
super.key,
|
||||||
this.onPopBack,
|
this.onPopBack,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
final Function? onPopBack;
|
final Function? onPopBack;
|
||||||
|
|
||||||
|
@ -26,6 +32,46 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
|
||||||
bool isEnableAutoBackup = backupState.backgroundBackup ||
|
bool isEnableAutoBackup = backupState.backgroundBackup ||
|
||||||
ref.watch(authenticationProvider).deviceInfo.isAutoBackup;
|
ref.watch(authenticationProvider).deviceInfo.isAutoBackup;
|
||||||
final ServerInfoState serverInfoState = ref.watch(serverInfoProvider);
|
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(
|
return AppBar(
|
||||||
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
||||||
|
@ -38,18 +84,8 @@ class HomePageAppBar extends ConsumerWidget with PreferredSizeWidget {
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Center(
|
||||||
top: 5,
|
child: buildProfilePhoto(),
|
||||||
child: IconButton(
|
|
||||||
splashRadius: 25,
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.face_outlined,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Scaffold.of(context).openDrawer();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (serverInfoState.isVersionMismatch)
|
if (serverInfoState.isVersionMismatch)
|
||||||
Positioned(
|
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/models/authentication_state.model.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.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/immich_loading_indicator.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/transparent_image.dart';
|
||||||
|
|
||||||
class ProfileDrawerHeader extends HookConsumerWidget {
|
class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
const ProfileDrawerHeader({
|
const ProfileDrawerHeader({
|
||||||
|
@ -26,6 +27,23 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
|
||||||
buildUserProfileImage() {
|
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) {
|
if (authState.profileImagePath.isEmpty) {
|
||||||
return const CircleAvatar(
|
return const CircleAvatar(
|
||||||
radius: 35,
|
radius: 35,
|
||||||
|
@ -36,16 +54,10 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
|
|
||||||
if (uploadProfileImageStatus == UploadProfileStatus.idle) {
|
if (uploadProfileImageStatus == UploadProfileStatus.idle) {
|
||||||
if (authState.profileImagePath.isNotEmpty) {
|
if (authState.profileImagePath.isNotEmpty) {
|
||||||
return CircleAvatar(
|
return userImage;
|
||||||
radius: 35,
|
|
||||||
backgroundImage: NetworkImage(
|
|
||||||
'$endpoint/user/profile-image/${authState.userId}?d=${dummy++}',
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return const CircleAvatar(
|
return const CircleAvatar(
|
||||||
radius: 35,
|
radius: 33,
|
||||||
backgroundImage: AssetImage('assets/immich-logo-no-outline.png'),
|
backgroundImage: AssetImage('assets/immich-logo-no-outline.png'),
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
);
|
);
|
||||||
|
@ -53,13 +65,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploadProfileImageStatus == UploadProfileStatus.success) {
|
if (uploadProfileImageStatus == UploadProfileStatus.success) {
|
||||||
return CircleAvatar(
|
return userImage;
|
||||||
radius: 35,
|
|
||||||
backgroundImage: NetworkImage(
|
|
||||||
'$endpoint/user/profile-image/${authState.userId}?d=${dummy++}',
|
|
||||||
),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploadProfileImageStatus == UploadProfileStatus.failure) {
|
if (uploadProfileImageStatus == UploadProfileStatus.failure) {
|
||||||
|
@ -98,7 +104,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
buildUserProfileImage();
|
// buildUserProfileImage();
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
|
@ -126,17 +132,17 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Stack(
|
GestureDetector(
|
||||||
|
onTap: pickUserProfileImage,
|
||||||
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
buildUserProfileImage(),
|
buildUserProfileImage(),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
right: -5,
|
right: -5,
|
||||||
child: GestureDetector(
|
|
||||||
onTap: pickUserProfileImage,
|
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.grey[100],
|
color: isDarkMode ? Colors.grey[700] : Colors.grey[100],
|
||||||
elevation: 3,
|
elevation: 3,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(50.0),
|
borderRadius: BorderRadius.circular(50.0),
|
||||||
|
@ -151,9 +157,9 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
"${authState.firstName} ${authState.lastName}",
|
"${authState.firstName} ${authState.lastName}",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
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": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.45.0",
|
"version": "1.46.1",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/bull": "^0.6.2",
|
"@nestjs/bull": "^0.6.2",
|
||||||
|
|
Loading…
Reference in a new issue