mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	refactor(mobile): app bar (#4687)
* refactor(mobile): add app bar to library and sharing * mobile: add app bar dialog * fix(mobile): refetch profile image only when path is changed * mobile: add server url to dialog * mobile: move trash to library app bar * replace discord link with github * user confirmation before sign out * edit some styles --------- Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
		| @@ -253,6 +253,8 @@ | |||||||
|   "profile_drawer_settings": "Settings", |   "profile_drawer_settings": "Settings", | ||||||
|   "profile_drawer_sign_out": "Sign Out", |   "profile_drawer_sign_out": "Sign Out", | ||||||
|   "profile_drawer_trash": "Trash", |   "profile_drawer_trash": "Trash", | ||||||
|  |   "profile_drawer_documentation": "Documentation", | ||||||
|  |   "profile_drawer_github": "GitHub", | ||||||
|   "recently_added_page_title": "Recently Added", |   "recently_added_page_title": "Recently Added", | ||||||
|   "search_bar_hint": "Search your photos", |   "search_bar_hint": "Search your photos", | ||||||
|   "search_page_categories": "Categories", |   "search_page_categories": "Categories", | ||||||
| @@ -277,6 +279,7 @@ | |||||||
|   "select_user_for_sharing_page_share_suggestions": "Suggestions", |   "select_user_for_sharing_page_share_suggestions": "Suggestions", | ||||||
|   "server_info_box_app_version": "App Version", |   "server_info_box_app_version": "App Version", | ||||||
|   "server_info_box_server_version": "Server Version", |   "server_info_box_server_version": "Server Version", | ||||||
|  |   "server_info_box_server_url": "Server URL", | ||||||
|   "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", |   "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", | ||||||
|   "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", |   "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", | ||||||
|   "setting_image_viewer_original_title": "Load original image", |   "setting_image_viewer_original_title": "Load original image", | ||||||
| @@ -366,5 +369,8 @@ | |||||||
|   "viewer_unstack": "Un-Stack", |   "viewer_unstack": "Un-Stack", | ||||||
|   "cache_settings_tile_title": "Local Storage", |   "cache_settings_tile_title": "Local Storage", | ||||||
|   "cache_settings_tile_subtitle": "Control the local storage behaviour", |   "cache_settings_tile_subtitle": "Control the local storage behaviour", | ||||||
|   "viewer_stack_use_as_main_asset": "Use as Main Asset" |   "viewer_stack_use_as_main_asset": "Use as Main Asset", | ||||||
|  |   "app_bar_signout_dialog_title": "Sign out", | ||||||
|  |   "app_bar_signout_dialog_content": "Are you sure you wanna sign out?", | ||||||
|  |   "app_bar_signout_dialog_ok": "Yes" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,12 +10,16 @@ import 'package:immich_mobile/routing/router.dart'; | |||||||
| import 'package:immich_mobile/shared/models/album.dart'; | import 'package:immich_mobile/shared/models/album.dart'; | ||||||
| import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; | import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart'; | ||||||
| import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; | import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/server_info.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/ui/immich_app_bar.dart'; | ||||||
|  |  | ||||||
| class LibraryPage extends HookConsumerWidget { | class LibraryPage extends HookConsumerWidget { | ||||||
|   const LibraryPage({Key? key}) : super(key: key); |   const LibraryPage({Key? key}) : super(key: key); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final trashEnabled = | ||||||
|  |         ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); | ||||||
|     final albums = ref.watch(albumProvider); |     final albums = ref.watch(albumProvider); | ||||||
|     var isDarkMode = Theme.of(context).brightness == Brightness.dark; |     var isDarkMode = Theme.of(context).brightness == Brightness.dark; | ||||||
|     var settings = ref.watch(appSettingsServiceProvider); |     var settings = ref.watch(appSettingsServiceProvider); | ||||||
| @@ -28,21 +32,6 @@ class LibraryPage extends HookConsumerWidget { | |||||||
|       [], |       [], | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     AppBar buildAppBar() { |  | ||||||
|       return AppBar( |  | ||||||
|         centerTitle: true, |  | ||||||
|         automaticallyImplyLeading: false, |  | ||||||
|         title: const Text( |  | ||||||
|           'IMMICH', |  | ||||||
|           style: TextStyle( |  | ||||||
|             fontFamily: 'SnowburstOne', |  | ||||||
|             fontWeight: FontWeight.bold, |  | ||||||
|             fontSize: 22, |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     final selectedAlbumSortOrder = |     final selectedAlbumSortOrder = | ||||||
|         useState(settings.getSetting(AppSettingsEnum.selectedAlbumSortOrder)); |         useState(settings.getSetting(AppSettingsEnum.selectedAlbumSortOrder)); | ||||||
|  |  | ||||||
| @@ -236,8 +225,23 @@ class LibraryPage extends HookConsumerWidget { | |||||||
|  |  | ||||||
|     final local = albums.where((a) => a.isLocal).toList(); |     final local = albums.where((a) => a.isLocal).toList(); | ||||||
|  |  | ||||||
|  |     Widget? shareTrashButton() { | ||||||
|  |       return trashEnabled | ||||||
|  |           ? InkWell( | ||||||
|  |               onTap: () => AutoRouter.of(context).push(const TrashRoute()), | ||||||
|  |               borderRadius: BorderRadius.circular(12), | ||||||
|  |               child: const Icon( | ||||||
|  |                 Icons.delete_rounded, | ||||||
|  |                 size: 25, | ||||||
|  |               ), | ||||||
|  |             ) | ||||||
|  |           : null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       appBar: buildAppBar(), |       appBar: ImmichAppBar( | ||||||
|  |         action: shareTrashButton(), | ||||||
|  |       ), | ||||||
|       body: CustomScrollView( |       body: CustomScrollView( | ||||||
|         slivers: [ |         slivers: [ | ||||||
|           SliverToBoxAdapter( |           SliverToBoxAdapter( | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ import 'package:immich_mobile/modules/partner/ui/partner_list.dart'; | |||||||
| import 'package:immich_mobile/routing/router.dart'; | import 'package:immich_mobile/routing/router.dart'; | ||||||
| import 'package:immich_mobile/shared/models/album.dart'; | import 'package:immich_mobile/shared/models/album.dart'; | ||||||
| import 'package:immich_mobile/shared/providers/user.provider.dart'; | import 'package:immich_mobile/shared/providers/user.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/ui/immich_app_bar.dart'; | ||||||
| import 'package:immich_mobile/shared/ui/immich_image.dart'; | import 'package:immich_mobile/shared/ui/immich_image.dart'; | ||||||
|  |  | ||||||
| class SharingPage extends HookConsumerWidget { | class SharingPage extends HookConsumerWidget { | ||||||
| @@ -167,32 +168,6 @@ class SharingPage extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     AppBar buildAppBar() { |  | ||||||
|       return AppBar( |  | ||||||
|         centerTitle: true, |  | ||||||
|         automaticallyImplyLeading: false, |  | ||||||
|         title: const Text( |  | ||||||
|           'IMMICH', |  | ||||||
|           style: TextStyle( |  | ||||||
|             fontFamily: 'SnowburstOne', |  | ||||||
|             fontWeight: FontWeight.bold, |  | ||||||
|             fontSize: 22, |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|         actions: [ |  | ||||||
|           IconButton( |  | ||||||
|             splashRadius: 25, |  | ||||||
|             iconSize: 20, |  | ||||||
|             icon: const Icon( |  | ||||||
|               Icons.swap_horizontal_circle_outlined, |  | ||||||
|               size: 20, |  | ||||||
|             ), |  | ||||||
|             onPressed: () => AutoRouter.of(context).push(const PartnerRoute()), |  | ||||||
|           ), |  | ||||||
|         ], |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     buildEmptyListIndication() { |     buildEmptyListIndication() { | ||||||
|       return SliverToBoxAdapter( |       return SliverToBoxAdapter( | ||||||
|         child: Padding( |         child: Padding( | ||||||
| @@ -241,8 +216,21 @@ class SharingPage extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     Widget sharePartnerButton() { | ||||||
|  |       return InkWell( | ||||||
|  |         onTap: () => AutoRouter.of(context).push(const PartnerRoute()), | ||||||
|  |         borderRadius: BorderRadius.circular(12), | ||||||
|  |         child: const Icon( | ||||||
|  |           Icons.swap_horizontal_circle_rounded, | ||||||
|  |           size: 25, | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       appBar: buildAppBar(), |       appBar: ImmichAppBar( | ||||||
|  |         action: sharePartnerButton(), | ||||||
|  |       ), | ||||||
|       body: CustomScrollView( |       body: CustomScrollView( | ||||||
|         slivers: [ |         slivers: [ | ||||||
|           SliverToBoxAdapter(child: buildTopBottons()), |           SliverToBoxAdapter(child: buildTopBottons()), | ||||||
|   | |||||||
| @@ -174,46 +174,6 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Widget buildStorageInformation() { |  | ||||||
|       return ListTile( |  | ||||||
|         leading: Icon( |  | ||||||
|           Icons.storage_rounded, |  | ||||||
|           color: Theme.of(context).primaryColor, |  | ||||||
|         ), |  | ||||||
|         title: const Text( |  | ||||||
|           "backup_controller_page_server_storage", |  | ||||||
|           style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), |  | ||||||
|         ).tr(), |  | ||||||
|         isThreeLine: true, |  | ||||||
|         subtitle: Padding( |  | ||||||
|           padding: const EdgeInsets.only(top: 8.0), |  | ||||||
|           child: Column( |  | ||||||
|             crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|             children: [ |  | ||||||
|               Padding( |  | ||||||
|                 padding: const EdgeInsets.only(top: 8.0), |  | ||||||
|                 child: LinearProgressIndicator( |  | ||||||
|                   minHeight: 10.0, |  | ||||||
|                   value: backupState.serverInfo.diskUsagePercentage / 100.0, |  | ||||||
|                   backgroundColor: Colors.grey, |  | ||||||
|                   color: Theme.of(context).primaryColor, |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|               Padding( |  | ||||||
|                 padding: const EdgeInsets.only(top: 12.0), |  | ||||||
|                 child: const Text('backup_controller_page_storage_format').tr( |  | ||||||
|                   args: [ |  | ||||||
|                     backupState.serverInfo.diskUse, |  | ||||||
|                     backupState.serverInfo.diskSize, |  | ||||||
|                   ], |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|             ], |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     ListTile buildAutoBackupController() { |     ListTile buildAutoBackupController() { | ||||||
|       final isAutoBackup = backupState.autoBackup; |       final isAutoBackup = backupState.autoBackup; | ||||||
|       final backUpOption = isAutoBackup |       final backUpOption = isAutoBackup | ||||||
| @@ -774,7 +734,6 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|             if (showBackupFix) const Divider(), |             if (showBackupFix) const Divider(), | ||||||
|             if (showBackupFix) buildCheckCorruptBackups(), |             if (showBackupFix) buildCheckCorruptBackups(), | ||||||
|             const Divider(), |             const Divider(), | ||||||
|             buildStorageInformation(), |  | ||||||
|             const Divider(), |             const Divider(), | ||||||
|             const CurrentUploadingAssetInfoBox(), |             const CurrentUploadingAssetInfoBox(), | ||||||
|             if (!hasExclusiveAccess) buildBackgroundBackupInfo(), |             if (!hasExclusiveAccess) buildBackgroundBackupInfo(), | ||||||
|   | |||||||
| @@ -1,171 +0,0 @@ | |||||||
| import 'package:auto_route/auto_route.dart'; |  | ||||||
| import 'package:flutter/material.dart'; |  | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; |  | ||||||
| import 'package:immich_mobile/shared/models/store.dart'; |  | ||||||
| import 'package:immich_mobile/shared/ui/user_circle_avatar.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'; |  | ||||||
| import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; |  | ||||||
| import 'package:immich_mobile/shared/models/server_info/server_info.model.dart'; |  | ||||||
| import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; |  | ||||||
| import 'package:immich_mobile/shared/providers/server_info.provider.dart'; |  | ||||||
|  |  | ||||||
| class HomePageAppBar extends ConsumerWidget implements PreferredSizeWidget { |  | ||||||
|   @override |  | ||||||
|   Size get preferredSize => const Size.fromHeight(kToolbarHeight); |  | ||||||
|  |  | ||||||
|   const HomePageAppBar({ |  | ||||||
|     super.key, |  | ||||||
|     this.onPopBack, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   final Function? onPopBack; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |  | ||||||
|     final BackUpState backupState = ref.watch(backupProvider); |  | ||||||
|     final bool isEnableAutoBackup = |  | ||||||
|         backupState.backgroundBackup || backupState.autoBackup; |  | ||||||
|     final ServerInfo serverInfoState = ref.watch(serverInfoProvider); |  | ||||||
|     AuthenticationState authState = ref.watch(authenticationProvider); |  | ||||||
|     final user = Store.tryGet(StoreKey.currentUser); |  | ||||||
|     buildProfilePhoto() { |  | ||||||
|       if (authState.profileImagePath.isEmpty || user == null) { |  | ||||||
|         return IconButton( |  | ||||||
|           splashRadius: 25, |  | ||||||
|           icon: const Icon( |  | ||||||
|             Icons.face_outlined, |  | ||||||
|             size: 30, |  | ||||||
|           ), |  | ||||||
|           onPressed: () { |  | ||||||
|             Scaffold.of(context).openDrawer(); |  | ||||||
|           }, |  | ||||||
|         ); |  | ||||||
|       } else { |  | ||||||
|         return InkWell( |  | ||||||
|           onTap: () { |  | ||||||
|             Scaffold.of(context).openDrawer(); |  | ||||||
|           }, |  | ||||||
|           child: UserCircleAvatar( |  | ||||||
|             radius: 18, |  | ||||||
|             size: 33, |  | ||||||
|             user: user, |  | ||||||
|           ), |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return AppBar( |  | ||||||
|       backgroundColor: Theme.of(context).appBarTheme.backgroundColor, |  | ||||||
|       shape: const RoundedRectangleBorder( |  | ||||||
|         borderRadius: BorderRadius.all( |  | ||||||
|           Radius.circular(5), |  | ||||||
|         ), |  | ||||||
|       ), |  | ||||||
|       leading: Builder( |  | ||||||
|         builder: (BuildContext context) { |  | ||||||
|           return Stack( |  | ||||||
|             children: [ |  | ||||||
|               Center( |  | ||||||
|                 child: buildProfilePhoto(), |  | ||||||
|               ), |  | ||||||
|               if (serverInfoState.isVersionMismatch) |  | ||||||
|                 Positioned( |  | ||||||
|                   bottom: 4, |  | ||||||
|                   right: 6, |  | ||||||
|                   child: GestureDetector( |  | ||||||
|                     onTap: () => Scaffold.of(context).openDrawer(), |  | ||||||
|                     child: Material( |  | ||||||
|                       // color: Colors.grey[200], |  | ||||||
|                       elevation: 1, |  | ||||||
|                       shape: RoundedRectangleBorder( |  | ||||||
|                         borderRadius: BorderRadius.circular(50.0), |  | ||||||
|                       ), |  | ||||||
|                       child: const Padding( |  | ||||||
|                         padding: EdgeInsets.all(2.0), |  | ||||||
|                         child: Icon( |  | ||||||
|                           Icons.info, |  | ||||||
|                           color: Color.fromARGB(255, 243, 188, 106), |  | ||||||
|                           size: 15, |  | ||||||
|                         ), |  | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|             ], |  | ||||||
|           ); |  | ||||||
|         }, |  | ||||||
|       ), |  | ||||||
|       title: const Text( |  | ||||||
|         'IMMICH', |  | ||||||
|         style: TextStyle( |  | ||||||
|           fontFamily: 'SnowburstOne', |  | ||||||
|           fontWeight: FontWeight.bold, |  | ||||||
|           fontSize: 22, |  | ||||||
|         ), |  | ||||||
|       ), |  | ||||||
|       actions: [ |  | ||||||
|         Stack( |  | ||||||
|           alignment: AlignmentDirectional.center, |  | ||||||
|           children: [ |  | ||||||
|             if (backupState.backupProgress == BackUpProgressEnum.inProgress) |  | ||||||
|               Positioned( |  | ||||||
|                 top: 10, |  | ||||||
|                 right: 12, |  | ||||||
|                 child: SizedBox( |  | ||||||
|                   height: 8, |  | ||||||
|                   width: 8, |  | ||||||
|                   child: CircularProgressIndicator( |  | ||||||
|                     strokeWidth: 1, |  | ||||||
|                     valueColor: AlwaysStoppedAnimation<Color>( |  | ||||||
|                       Theme.of(context).primaryColor, |  | ||||||
|                     ), |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|             IconButton( |  | ||||||
|               splashRadius: 25, |  | ||||||
|               iconSize: 30, |  | ||||||
|               icon: isEnableAutoBackup |  | ||||||
|                   ? const Icon( |  | ||||||
|                       Icons.backup_rounded, |  | ||||||
|                     ) |  | ||||||
|                   : Badge( |  | ||||||
|                       padding: const EdgeInsets.all(4), |  | ||||||
|                       backgroundColor: Colors.white, |  | ||||||
|                       label: const Icon( |  | ||||||
|                         Icons.cloud_off_rounded, |  | ||||||
|                         size: 8, |  | ||||||
|                         color: Colors.indigo, |  | ||||||
|                       ), |  | ||||||
|                       child: Icon( |  | ||||||
|                         Icons.backup_rounded, |  | ||||||
|                         color: Theme.of(context).primaryColor, |  | ||||||
|                       ), |  | ||||||
|                     ), |  | ||||||
|               onPressed: () async { |  | ||||||
|                 var onPop = await AutoRouter.of(context) |  | ||||||
|                     .push(const BackupControllerRoute()); |  | ||||||
|  |  | ||||||
|                 if (onPop != null && onPop == true) { |  | ||||||
|                   onPopBack!(); |  | ||||||
|                 } |  | ||||||
|               }, |  | ||||||
|             ), |  | ||||||
|             if (backupState.backupProgress == BackUpProgressEnum.inProgress) |  | ||||||
|               Positioned( |  | ||||||
|                 bottom: 5, |  | ||||||
|                 child: Text( |  | ||||||
|                   '${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}', |  | ||||||
|                   style: |  | ||||||
|                       const TextStyle(fontSize: 9, fontWeight: FontWeight.bold), |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|           ], |  | ||||||
|         ), |  | ||||||
|       ], |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,144 +0,0 @@ | |||||||
| import 'package:auto_route/auto_route.dart'; |  | ||||||
| import 'package:easy_localization/easy_localization.dart'; |  | ||||||
| import 'package:flutter/material.dart'; |  | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; |  | ||||||
| import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; |  | ||||||
| import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart'; |  | ||||||
| import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer_header.dart'; |  | ||||||
| import 'package:immich_mobile/modules/home/ui/profile_drawer/server_info_box.dart'; |  | ||||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; |  | ||||||
| import 'package:immich_mobile/routing/router.dart'; |  | ||||||
| import 'package:immich_mobile/shared/providers/asset.provider.dart'; |  | ||||||
| import 'package:immich_mobile/shared/providers/server_info.provider.dart'; |  | ||||||
| import 'package:immich_mobile/shared/providers/websocket.provider.dart'; |  | ||||||
|  |  | ||||||
| class ProfileDrawer extends HookConsumerWidget { |  | ||||||
|   const ProfileDrawer({Key? key}) : super(key: key); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |  | ||||||
|     final trashEnabled = |  | ||||||
|         ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); |  | ||||||
|  |  | ||||||
|     buildSignOutButton() { |  | ||||||
|       return ListTile( |  | ||||||
|         leading: SizedBox( |  | ||||||
|           height: double.infinity, |  | ||||||
|           child: Icon( |  | ||||||
|             Icons.logout_rounded, |  | ||||||
|             color: Theme.of(context).textTheme.labelMedium?.color, |  | ||||||
|             size: 20, |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|         title: Text( |  | ||||||
|           "profile_drawer_sign_out", |  | ||||||
|           style: Theme.of(context) |  | ||||||
|               .textTheme |  | ||||||
|               .labelLarge |  | ||||||
|               ?.copyWith(fontWeight: FontWeight.bold), |  | ||||||
|         ).tr(), |  | ||||||
|         onTap: () async { |  | ||||||
|           await ref.watch(authenticationProvider.notifier).logout(); |  | ||||||
|  |  | ||||||
|           ref.read(manualUploadProvider.notifier).cancelBackup(); |  | ||||||
|           ref.watch(backupProvider.notifier).cancelBackup(); |  | ||||||
|           ref.watch(assetProvider.notifier).clearAllAsset(); |  | ||||||
|           ref.watch(websocketProvider.notifier).disconnect(); |  | ||||||
|           AutoRouter.of(context).replace(const LoginRoute()); |  | ||||||
|         }, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     buildSettingButton() { |  | ||||||
|       return ListTile( |  | ||||||
|         leading: SizedBox( |  | ||||||
|           height: double.infinity, |  | ||||||
|           child: Icon( |  | ||||||
|             Icons.settings_rounded, |  | ||||||
|             color: Theme.of(context).textTheme.labelMedium?.color, |  | ||||||
|             size: 20, |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|         title: Text( |  | ||||||
|           "profile_drawer_settings", |  | ||||||
|           style: Theme.of(context) |  | ||||||
|               .textTheme |  | ||||||
|               .labelLarge |  | ||||||
|               ?.copyWith(fontWeight: FontWeight.bold), |  | ||||||
|         ).tr(), |  | ||||||
|         onTap: () { |  | ||||||
|           AutoRouter.of(context).push(const SettingsRoute()); |  | ||||||
|         }, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     buildAppLogButton() { |  | ||||||
|       return ListTile( |  | ||||||
|         leading: SizedBox( |  | ||||||
|           height: double.infinity, |  | ||||||
|           child: Icon( |  | ||||||
|             Icons.assignment_outlined, |  | ||||||
|             color: Theme.of(context).textTheme.labelMedium?.color, |  | ||||||
|             size: 20, |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|         title: Text( |  | ||||||
|           "profile_drawer_app_logs", |  | ||||||
|           style: Theme.of(context) |  | ||||||
|               .textTheme |  | ||||||
|               .labelLarge |  | ||||||
|               ?.copyWith(fontWeight: FontWeight.bold), |  | ||||||
|         ).tr(), |  | ||||||
|         onTap: () { |  | ||||||
|           AutoRouter.of(context).push(const AppLogRoute()); |  | ||||||
|         }, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     buildTrashButton() { |  | ||||||
|       return ListTile( |  | ||||||
|         leading: SizedBox( |  | ||||||
|           height: double.infinity, |  | ||||||
|           child: Icon( |  | ||||||
|             Icons.delete_rounded, |  | ||||||
|             color: Theme.of(context).textTheme.labelMedium?.color, |  | ||||||
|             size: 20, |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|         title: Text( |  | ||||||
|           "profile_drawer_trash", |  | ||||||
|           style: Theme.of(context) |  | ||||||
|               .textTheme |  | ||||||
|               .labelLarge |  | ||||||
|               ?.copyWith(fontWeight: FontWeight.bold), |  | ||||||
|         ).tr(), |  | ||||||
|         onTap: () { |  | ||||||
|           AutoRouter.of(context).push(const TrashRoute()); |  | ||||||
|         }, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return Drawer( |  | ||||||
|       shape: const RoundedRectangleBorder( |  | ||||||
|         borderRadius: BorderRadius.zero, |  | ||||||
|       ), |  | ||||||
|       child: Column( |  | ||||||
|         mainAxisAlignment: MainAxisAlignment.spaceBetween, |  | ||||||
|         children: [ |  | ||||||
|           ListView( |  | ||||||
|             shrinkWrap: true, |  | ||||||
|             padding: EdgeInsets.zero, |  | ||||||
|             children: [ |  | ||||||
|               const ProfileDrawerHeader(), |  | ||||||
|               buildSettingButton(), |  | ||||||
|               buildAppLogButton(), |  | ||||||
|               if (trashEnabled) buildTrashButton(), |  | ||||||
|               buildSignOutButton(), |  | ||||||
|             ], |  | ||||||
|           ), |  | ||||||
|           const ServerInfoBox(), |  | ||||||
|         ], |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,126 +0,0 @@ | |||||||
| import 'package:flutter/material.dart'; |  | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart'; |  | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; |  | ||||||
| import 'package:immich_mobile/shared/models/server_info/server_info.model.dart'; |  | ||||||
| import 'package:easy_localization/easy_localization.dart'; |  | ||||||
| import 'package:immich_mobile/shared/providers/server_info.provider.dart'; |  | ||||||
| import 'package:package_info_plus/package_info_plus.dart'; |  | ||||||
|  |  | ||||||
| class ServerInfoBox extends HookConsumerWidget { |  | ||||||
|   const ServerInfoBox({ |  | ||||||
|     Key? key, |  | ||||||
|   }) : super(key: key); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |  | ||||||
|     ServerInfo serverInfoState = ref.watch(serverInfoProvider); |  | ||||||
|  |  | ||||||
|     final appInfo = useState({}); |  | ||||||
|  |  | ||||||
|     getPackageInfo() async { |  | ||||||
|       PackageInfo packageInfo = await PackageInfo.fromPlatform(); |  | ||||||
|  |  | ||||||
|       appInfo.value = { |  | ||||||
|         "version": packageInfo.version, |  | ||||||
|         "buildNumber": packageInfo.buildNumber, |  | ||||||
|       }; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     useEffect( |  | ||||||
|       () { |  | ||||||
|         getPackageInfo(); |  | ||||||
|         return null; |  | ||||||
|       }, |  | ||||||
|       [], |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     return Padding( |  | ||||||
|       padding: const EdgeInsets.all(8.0), |  | ||||||
|       child: Card( |  | ||||||
|         elevation: 0, |  | ||||||
|         color: Theme.of(context).scaffoldBackgroundColor, |  | ||||||
|         shape: RoundedRectangleBorder( |  | ||||||
|           borderRadius: BorderRadius.circular(5), // if you need this |  | ||||||
|           side: const BorderSide( |  | ||||||
|             color: Color.fromARGB(101, 201, 201, 201), |  | ||||||
|             width: 1, |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|         child: Padding( |  | ||||||
|           padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8), |  | ||||||
|           child: Column( |  | ||||||
|             crossAxisAlignment: CrossAxisAlignment.center, |  | ||||||
|             children: [ |  | ||||||
|               Padding( |  | ||||||
|                 padding: const EdgeInsets.all(8.0), |  | ||||||
|                 child: Text( |  | ||||||
|                   serverInfoState.isVersionMismatch |  | ||||||
|                       ? serverInfoState.versionMismatchErrorMessage |  | ||||||
|                       : "profile_drawer_client_server_up_to_date".tr(), |  | ||||||
|                   textAlign: TextAlign.center, |  | ||||||
|                   style: TextStyle( |  | ||||||
|                     fontSize: 11, |  | ||||||
|                     color: Theme.of(context).primaryColor, |  | ||||||
|                     fontWeight: FontWeight.w600, |  | ||||||
|                   ), |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|               const Divider( |  | ||||||
|                 color: Color.fromARGB(101, 201, 201, 201), |  | ||||||
|                 thickness: 1, |  | ||||||
|               ), |  | ||||||
|               Row( |  | ||||||
|                 mainAxisAlignment: MainAxisAlignment.spaceBetween, |  | ||||||
|                 children: [ |  | ||||||
|                   Text( |  | ||||||
|                     "server_info_box_app_version".tr(), |  | ||||||
|                     style: TextStyle( |  | ||||||
|                       fontSize: 11, |  | ||||||
|                       color: Colors.grey[500], |  | ||||||
|                       fontWeight: FontWeight.bold, |  | ||||||
|                     ), |  | ||||||
|                   ), |  | ||||||
|                   Text( |  | ||||||
|                     "${appInfo.value["version"]} build.${appInfo.value["buildNumber"]}", |  | ||||||
|                     style: TextStyle( |  | ||||||
|                       fontSize: 11, |  | ||||||
|                       color: Colors.grey[500], |  | ||||||
|                       fontWeight: FontWeight.bold, |  | ||||||
|                     ), |  | ||||||
|                   ), |  | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|               const Divider( |  | ||||||
|                 color: Color.fromARGB(101, 201, 201, 201), |  | ||||||
|                 thickness: 1, |  | ||||||
|               ), |  | ||||||
|               Row( |  | ||||||
|                 mainAxisAlignment: MainAxisAlignment.spaceBetween, |  | ||||||
|                 children: [ |  | ||||||
|                   Text( |  | ||||||
|                     "server_info_box_server_version".tr(), |  | ||||||
|                     style: TextStyle( |  | ||||||
|                       fontSize: 11, |  | ||||||
|                       color: Colors.grey[500], |  | ||||||
|                       fontWeight: FontWeight.bold, |  | ||||||
|                     ), |  | ||||||
|                   ), |  | ||||||
|                   Text( |  | ||||||
|                     serverInfoState.serverVersion.major > 0 |  | ||||||
|                         ? "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}" |  | ||||||
|                         : "?", |  | ||||||
|                     style: TextStyle( |  | ||||||
|                       fontSize: 11, |  | ||||||
|                       color: Colors.grey[500], |  | ||||||
|                       fontWeight: FontWeight.bold, |  | ||||||
|                     ), |  | ||||||
|                   ), |  | ||||||
|                 ], |  | ||||||
|               ), |  | ||||||
|             ], |  | ||||||
|           ), |  | ||||||
|         ), |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -17,9 +17,7 @@ import 'package:immich_mobile/modules/home/models/selection_state.dart'; | |||||||
| import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart'; | import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart'; | ||||||
| import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; | import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; | ||||||
| import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart'; | import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart'; | ||||||
| import 'package:immich_mobile/modules/home/ui/home_page_app_bar.dart'; |  | ||||||
| import 'package:immich_mobile/modules/memories/ui/memory_lane.dart'; | import 'package:immich_mobile/modules/memories/ui/memory_lane.dart'; | ||||||
| import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart'; |  | ||||||
| import 'package:immich_mobile/routing/router.dart'; | import 'package:immich_mobile/routing/router.dart'; | ||||||
| import 'package:immich_mobile/shared/models/album.dart'; | import 'package:immich_mobile/shared/models/album.dart'; | ||||||
| import 'package:immich_mobile/shared/models/asset.dart'; | import 'package:immich_mobile/shared/models/asset.dart'; | ||||||
| @@ -27,6 +25,7 @@ import 'package:immich_mobile/shared/providers/asset.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/providers/user.provider.dart'; | import 'package:immich_mobile/shared/providers/user.provider.dart'; | ||||||
| import 'package:immich_mobile/shared/providers/websocket.provider.dart'; | import 'package:immich_mobile/shared/providers/websocket.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/ui/immich_app_bar.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/immich_toast.dart'; | import 'package:immich_mobile/shared/ui/immich_toast.dart'; | ||||||
| import 'package:immich_mobile/utils/selection_handlers.dart'; | import 'package:immich_mobile/utils/selection_handlers.dart'; | ||||||
| @@ -74,10 +73,6 @@ class HomePage extends HookConsumerWidget { | |||||||
|       [], |       [], | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     void reloadAllAsset() { |  | ||||||
|       ref.watch(assetProvider.notifier).getAllAsset(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Widget buildBody() { |     Widget buildBody() { | ||||||
|       void selectionListener( |       void selectionListener( | ||||||
|         bool multiselect, |         bool multiselect, | ||||||
| @@ -375,10 +370,7 @@ class HomePage extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       appBar: !selectionEnabledHook.value |       appBar: !selectionEnabledHook.value ? const ImmichAppBar() : null, | ||||||
|           ? HomePageAppBar(onPopBack: reloadAllAsset) |  | ||||||
|           : null, |  | ||||||
|       drawer: const ProfileDrawer(), |  | ||||||
|       body: buildBody(), |       body: buildBody(), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -16,7 +16,8 @@ class MemoryLane extends HookConsumerWidget { | |||||||
|     final memoryLane = memoryLaneFutureProvider |     final memoryLane = memoryLaneFutureProvider | ||||||
|         .whenData( |         .whenData( | ||||||
|           (memories) => memories != null |           (memories) => memories != null | ||||||
|               ? SizedBox( |               ? Container( | ||||||
|  |                   margin: const EdgeInsets.only(top: 10), | ||||||
|                   height: 200, |                   height: 200, | ||||||
|                   child: ListView.builder( |                   child: ListView.builder( | ||||||
|                     scrollDirection: Axis.horizontal, |                     scrollDirection: Axis.horizontal, | ||||||
|   | |||||||
| @@ -133,10 +133,7 @@ part 'router.gr.dart'; | |||||||
|         DuplicateGuard, |         DuplicateGuard, | ||||||
|       ], |       ], | ||||||
|     ), |     ), | ||||||
|     CustomRoute( |     AutoRoute(page: AppLogPage, guards: [DuplicateGuard]), | ||||||
|       page: AppLogPage, |  | ||||||
|       transitionsBuilder: TransitionsBuilders.slideBottom, |  | ||||||
|     ), |  | ||||||
|     AutoRoute( |     AutoRoute( | ||||||
|       page: AppLogDetailPage, |       page: AppLogDetailPage, | ||||||
|     ), |     ), | ||||||
|   | |||||||
| @@ -231,12 +231,9 @@ class _$AppRouter extends RootStackRouter { | |||||||
|       ); |       ); | ||||||
|     }, |     }, | ||||||
|     AppLogRoute.name: (routeData) { |     AppLogRoute.name: (routeData) { | ||||||
|       return CustomPage<dynamic>( |       return MaterialPageX<dynamic>( | ||||||
|         routeData: routeData, |         routeData: routeData, | ||||||
|         child: const AppLogPage(), |         child: const AppLogPage(), | ||||||
|         transitionsBuilder: TransitionsBuilders.slideBottom, |  | ||||||
|         opaque: true, |  | ||||||
|         barrierDismissible: false, |  | ||||||
|       ); |       ); | ||||||
|     }, |     }, | ||||||
|     AppLogDetailRoute.name: (routeData) { |     AppLogDetailRoute.name: (routeData) { | ||||||
| @@ -583,6 +580,7 @@ class _$AppRouter extends RootStackRouter { | |||||||
|         RouteConfig( |         RouteConfig( | ||||||
|           AppLogRoute.name, |           AppLogRoute.name, | ||||||
|           path: '/app-log-page', |           path: '/app-log-page', | ||||||
|  |           guards: [duplicateGuard], | ||||||
|         ), |         ), | ||||||
|         RouteConfig( |         RouteConfig( | ||||||
|           AppLogDetailRoute.name, |           AppLogDetailRoute.name, | ||||||
|   | |||||||
							
								
								
									
										263
									
								
								mobile/lib/shared/ui/app_bar_dialog/app_bar_dialog.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								mobile/lib/shared/ui/app_bar_dialog/app_bar_dialog.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,263 @@ | |||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart'; | ||||||
|  | import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||||
|  | import 'package:immich_mobile/routing/router.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/asset.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/user.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/websocket.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/ui/app_bar_dialog/app_bar_profile_info.dart'; | ||||||
|  | import 'package:immich_mobile/shared/ui/app_bar_dialog/app_bar_server_info.dart'; | ||||||
|  | import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; | ||||||
|  | import 'package:url_launcher/url_launcher.dart'; | ||||||
|  |  | ||||||
|  | class ImmichAppBarDialog extends HookConsumerWidget { | ||||||
|  |   const ImmichAppBarDialog({super.key}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     BackUpState backupState = ref.watch(backupProvider); | ||||||
|  |     final theme = Theme.of(context); | ||||||
|  |     bool isDarkTheme = theme.brightness == Brightness.dark; | ||||||
|  |     bool isHorizontal = MediaQuery.of(context).size.width > 600; | ||||||
|  |     final horizontalPadding = isHorizontal ? 100.0 : 20.0; | ||||||
|  |     final user = ref.watch(currentUserProvider); | ||||||
|  |  | ||||||
|  |     useEffect( | ||||||
|  |       () { | ||||||
|  |         ref.read(backupProvider.notifier).updateServerInfo(); | ||||||
|  |         return null; | ||||||
|  |       }, | ||||||
|  |       [user], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     buildTopRow() { | ||||||
|  |       return Row( | ||||||
|  |         children: [ | ||||||
|  |           InkWell( | ||||||
|  |             onTap: () => Navigator.of(context).pop(), | ||||||
|  |             child: const Icon( | ||||||
|  |               Icons.close, | ||||||
|  |               size: 20, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           Expanded( | ||||||
|  |             child: Align( | ||||||
|  |               alignment: Alignment.center, | ||||||
|  |               child: Text( | ||||||
|  |                 'IMMICH', | ||||||
|  |                 style: TextStyle( | ||||||
|  |                   fontFamily: 'SnowburstOne', | ||||||
|  |                   fontWeight: FontWeight.bold, | ||||||
|  |                   color: Theme.of(context).primaryColor, | ||||||
|  |                   fontSize: 15, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildActionButton(IconData icon, String text, Function() onTap) { | ||||||
|  |       return ListTile( | ||||||
|  |         dense: true, | ||||||
|  |         visualDensity: VisualDensity.standard, | ||||||
|  |         contentPadding: const EdgeInsets.only(left: 30), | ||||||
|  |         minLeadingWidth: 40, | ||||||
|  |         leading: SizedBox( | ||||||
|  |           child: Icon( | ||||||
|  |             icon, | ||||||
|  |             color: theme.textTheme.labelMedium?.color, | ||||||
|  |             size: 20, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         title: Text( | ||||||
|  |           text, | ||||||
|  |           style: | ||||||
|  |               theme.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), | ||||||
|  |         ).tr(), | ||||||
|  |         onTap: onTap, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildSettingButton() { | ||||||
|  |       return buildActionButton( | ||||||
|  |         Icons.settings_rounded, | ||||||
|  |         "profile_drawer_settings", | ||||||
|  |         () => AutoRouter.of(context).push(const SettingsRoute()), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildAppLogButton() { | ||||||
|  |       return buildActionButton( | ||||||
|  |         Icons.assignment_outlined, | ||||||
|  |         "profile_drawer_app_logs", | ||||||
|  |         () => AutoRouter.of(context).push(const AppLogRoute()), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildSignOutButton() { | ||||||
|  |       return buildActionButton( | ||||||
|  |         Icons.logout_rounded, | ||||||
|  |         "profile_drawer_sign_out", | ||||||
|  |         () async { | ||||||
|  |           showDialog( | ||||||
|  |             context: context, | ||||||
|  |             builder: (BuildContext ctx) { | ||||||
|  |               return ConfirmDialog( | ||||||
|  |                 title: "app_bar_signout_dialog_title", | ||||||
|  |                 content: "app_bar_signout_dialog_content", | ||||||
|  |                 ok: "app_bar_signout_dialog_ok", | ||||||
|  |                 onOk: () async { | ||||||
|  |                   await ref.watch(authenticationProvider.notifier).logout(); | ||||||
|  |  | ||||||
|  |                   ref.read(manualUploadProvider.notifier).cancelBackup(); | ||||||
|  |                   ref.watch(backupProvider.notifier).cancelBackup(); | ||||||
|  |                   ref.watch(assetProvider.notifier).clearAllAsset(); | ||||||
|  |                   ref.watch(websocketProvider.notifier).disconnect(); | ||||||
|  |                   AutoRouter.of(context).replace(const LoginRoute()); | ||||||
|  |                 }, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Widget buildStorageInformation() { | ||||||
|  |       return Padding( | ||||||
|  |         padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 3), | ||||||
|  |         child: Container( | ||||||
|  |           padding: const EdgeInsets.symmetric(vertical: 4), | ||||||
|  |           decoration: BoxDecoration( | ||||||
|  |             color: isDarkTheme | ||||||
|  |                 ? Theme.of(context).scaffoldBackgroundColor | ||||||
|  |                 : const Color.fromARGB(255, 225, 229, 240), | ||||||
|  |           ), | ||||||
|  |           child: ListTile( | ||||||
|  |             minLeadingWidth: 50, | ||||||
|  |             leading: Icon( | ||||||
|  |               Icons.storage_rounded, | ||||||
|  |               color: theme.primaryColor, | ||||||
|  |             ), | ||||||
|  |             title: const Text( | ||||||
|  |               "backup_controller_page_server_storage", | ||||||
|  |               style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), | ||||||
|  |             ).tr(), | ||||||
|  |             isThreeLine: true, | ||||||
|  |             subtitle: Padding( | ||||||
|  |               padding: const EdgeInsets.only(top: 8.0), | ||||||
|  |               child: Column( | ||||||
|  |                 crossAxisAlignment: CrossAxisAlignment.start, | ||||||
|  |                 children: [ | ||||||
|  |                   Padding( | ||||||
|  |                     padding: const EdgeInsets.only(top: 8.0), | ||||||
|  |                     child: LinearProgressIndicator( | ||||||
|  |                       minHeight: 5.0, | ||||||
|  |                       value: backupState.serverInfo.diskUsagePercentage / 100.0, | ||||||
|  |                       backgroundColor: Colors.grey, | ||||||
|  |                       color: theme.primaryColor, | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   Padding( | ||||||
|  |                     padding: const EdgeInsets.only(top: 12.0), | ||||||
|  |                     child: | ||||||
|  |                         const Text('backup_controller_page_storage_format').tr( | ||||||
|  |                       args: [ | ||||||
|  |                         backupState.serverInfo.diskUse, | ||||||
|  |                         backupState.serverInfo.diskSize, | ||||||
|  |                       ], | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildFooter() { | ||||||
|  |       return Padding( | ||||||
|  |         padding: const EdgeInsets.only(top: 10, bottom: 20), | ||||||
|  |         child: Row( | ||||||
|  |           mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |           children: [ | ||||||
|  |             InkWell( | ||||||
|  |               onTap: () { | ||||||
|  |                 Navigator.of(context).pop(); | ||||||
|  |                 launchUrl( | ||||||
|  |                   Uri.parse('https://immich.app'), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |               child: Text( | ||||||
|  |                 "profile_drawer_documentation", | ||||||
|  |                 style: Theme.of(context).textTheme.bodySmall, | ||||||
|  |               ).tr(), | ||||||
|  |             ), | ||||||
|  |             const SizedBox( | ||||||
|  |               width: 20, | ||||||
|  |               child: Text( | ||||||
|  |                 "•", | ||||||
|  |                 textAlign: TextAlign.center, | ||||||
|  |               ), | ||||||
|  |             ), | ||||||
|  |             InkWell( | ||||||
|  |               onTap: () { | ||||||
|  |                 Navigator.of(context).pop(); | ||||||
|  |                 launchUrl( | ||||||
|  |                   Uri.parse('https://github.com/immich-app/immich'), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |               child: Text( | ||||||
|  |                 "profile_drawer_github", | ||||||
|  |                 style: Theme.of(context).textTheme.bodySmall, | ||||||
|  |               ).tr(), | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return Dialog( | ||||||
|  |       clipBehavior: Clip.hardEdge, | ||||||
|  |       alignment: Alignment.topCenter, | ||||||
|  |       insetPadding: EdgeInsets.only( | ||||||
|  |         top: isHorizontal ? 20 : 60, | ||||||
|  |         left: horizontalPadding, | ||||||
|  |         right: horizontalPadding, | ||||||
|  |         bottom: isHorizontal ? 20 : 100, | ||||||
|  |       ), | ||||||
|  |       backgroundColor: theme.cardColor, | ||||||
|  |       shape: RoundedRectangleBorder( | ||||||
|  |         borderRadius: BorderRadius.circular(20), | ||||||
|  |       ), | ||||||
|  |       child: SizedBox( | ||||||
|  |         child: SingleChildScrollView( | ||||||
|  |           child: Column( | ||||||
|  |             mainAxisSize: MainAxisSize.min, | ||||||
|  |             children: [ | ||||||
|  |               Container( | ||||||
|  |                 padding: const EdgeInsets.all(20), | ||||||
|  |                 child: buildTopRow(), | ||||||
|  |               ), | ||||||
|  |               const AppBarProfileInfoBox(), | ||||||
|  |               buildStorageInformation(), | ||||||
|  |               const AppBarServerInfo(), | ||||||
|  |               buildAppLogButton(), | ||||||
|  |               buildSettingButton(), | ||||||
|  |               buildSignOutButton(), | ||||||
|  |               buildFooter(), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,5 +1,4 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:flutter_hooks/flutter_hooks.dart' hide Store; |  | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:image_picker/image_picker.dart'; | import 'package:image_picker/image_picker.dart'; | ||||||
| import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart'; | import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart'; | ||||||
| @@ -9,8 +8,8 @@ import 'package:immich_mobile/modules/login/models/authentication_state.model.da | |||||||
| 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'; | ||||||
| 
 | 
 | ||||||
| class ProfileDrawerHeader extends HookConsumerWidget { | class AppBarProfileInfoBox extends HookConsumerWidget { | ||||||
|   const ProfileDrawerHeader({ |   const AppBarProfileInfoBox({ | ||||||
|     Key? key, |     Key? key, | ||||||
|   }) : super(key: key); |   }) : super(key: key); | ||||||
| 
 | 
 | ||||||
| @@ -23,30 +22,24 @@ class ProfileDrawerHeader extends HookConsumerWidget { | |||||||
|     final user = Store.tryGet(StoreKey.currentUser); |     final user = Store.tryGet(StoreKey.currentUser); | ||||||
| 
 | 
 | ||||||
|     buildUserProfileImage() { |     buildUserProfileImage() { | ||||||
|       if (authState.profileImagePath.isEmpty || user == null) { |       const immichImage = CircleAvatar( | ||||||
|         return const CircleAvatar( |         radius: 20, | ||||||
|           radius: 35, |  | ||||||
|         backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), |         backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), | ||||||
|         backgroundColor: Colors.transparent, |         backgroundColor: Colors.transparent, | ||||||
|       ); |       ); | ||||||
|  | 
 | ||||||
|  |       if (authState.profileImagePath.isEmpty || user == null) { | ||||||
|  |         return immichImage; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       var userImage = UserCircleAvatar( |       final userImage = UserCircleAvatar( | ||||||
|         radius: 35, |         radius: 20, | ||||||
|         size: 66, |         size: 40, | ||||||
|         user: user, |         user: user, | ||||||
|       ); |       ); | ||||||
| 
 | 
 | ||||||
|       if (uploadProfileImageStatus == UploadProfileStatus.idle) { |       if (uploadProfileImageStatus == UploadProfileStatus.idle) { | ||||||
|         if (authState.profileImagePath.isNotEmpty) { |         return authState.profileImagePath.isNotEmpty ? userImage : immichImage; | ||||||
|           return userImage; |  | ||||||
|         } else { |  | ||||||
|           return const CircleAvatar( |  | ||||||
|             radius: 33, |  | ||||||
|             backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), |  | ||||||
|             backgroundColor: Colors.transparent, |  | ||||||
|           ); |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (uploadProfileImageStatus == UploadProfileStatus.success) { |       if (uploadProfileImageStatus == UploadProfileStatus.success) { | ||||||
| @@ -54,18 +47,18 @@ class ProfileDrawerHeader extends HookConsumerWidget { | |||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (uploadProfileImageStatus == UploadProfileStatus.failure) { |       if (uploadProfileImageStatus == UploadProfileStatus.failure) { | ||||||
|         return const CircleAvatar( |         return immichImage; | ||||||
|           radius: 35, |  | ||||||
|           backgroundImage: AssetImage('assets/immich-logo-no-outline.png'), |  | ||||||
|           backgroundColor: Colors.transparent, |  | ||||||
|         ); |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       if (uploadProfileImageStatus == UploadProfileStatus.loading) { |       if (uploadProfileImageStatus == UploadProfileStatus.loading) { | ||||||
|         return const ImmichLoadingIndicator(); |         return const SizedBox( | ||||||
|  |           height: 40, | ||||||
|  |           width: 40, | ||||||
|  |           child: ImmichLoadingIndicator(borderRadius: 20), | ||||||
|  |         ); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       return const SizedBox(); |       return immichImage; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pickUserProfileImage() async { |     pickUserProfileImage() async { | ||||||
| @@ -80,54 +73,45 @@ class ProfileDrawerHeader extends HookConsumerWidget { | |||||||
|             await ref.watch(uploadProfileImageProvider.notifier).upload(image); |             await ref.watch(uploadProfileImageProvider.notifier).upload(image); | ||||||
| 
 | 
 | ||||||
|         if (success) { |         if (success) { | ||||||
|  |           final profileImagePath = | ||||||
|  |               ref.read(uploadProfileImageProvider).profileImagePath; | ||||||
|           ref.watch(authenticationProvider.notifier).updateUserProfileImagePath( |           ref.watch(authenticationProvider.notifier).updateUserProfileImagePath( | ||||||
|                 ref.read(uploadProfileImageProvider).profileImagePath, |                 profileImagePath, | ||||||
|               ); |               ); | ||||||
|  |           if (user != null) { | ||||||
|  |             user.profileImagePath = profileImagePath; | ||||||
|  |             Store.put(StoreKey.currentUser, user); | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     useEffect( |     return Padding( | ||||||
|       () { |       padding: const EdgeInsets.symmetric(horizontal: 10.0), | ||||||
|         // buildUserProfileImage(); |       child: Container( | ||||||
|         return null; |         width: double.infinity, | ||||||
|       }, |  | ||||||
|       [], |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     return DrawerHeader( |  | ||||||
|         decoration: BoxDecoration( |         decoration: BoxDecoration( | ||||||
|         gradient: LinearGradient( |           color: Theme.of(context).brightness == Brightness.dark | ||||||
|           colors: isDarkMode |               ? Theme.of(context).scaffoldBackgroundColor | ||||||
|               ? [ |               : const Color.fromARGB(255, 225, 229, 240), | ||||||
|                   const Color.fromARGB(255, 22, 25, 48), |           borderRadius: const BorderRadius.only( | ||||||
|                   const Color.fromARGB(255, 13, 13, 13), |             topLeft: Radius.circular(10), | ||||||
|                   const Color.fromARGB(255, 0, 0, 0), |             topRight: Radius.circular(10), | ||||||
|                 ] |  | ||||||
|               : [ |  | ||||||
|                   const Color.fromARGB(255, 216, 219, 238), |  | ||||||
|                   const Color.fromARGB(255, 242, 242, 242), |  | ||||||
|                   Colors.white, |  | ||||||
|                 ], |  | ||||||
|           begin: Alignment.centerRight, |  | ||||||
|           end: Alignment.centerLeft, |  | ||||||
|           ), |           ), | ||||||
|         ), |         ), | ||||||
|       child: Column( |         child: ListTile( | ||||||
|         mainAxisAlignment: MainAxisAlignment.start, |           minLeadingWidth: 50, | ||||||
|         crossAxisAlignment: CrossAxisAlignment.start, |           leading: GestureDetector( | ||||||
|         children: [ |  | ||||||
|           GestureDetector( |  | ||||||
|             onTap: pickUserProfileImage, |             onTap: pickUserProfileImage, | ||||||
|             child: Stack( |             child: Stack( | ||||||
|               clipBehavior: Clip.none, |               clipBehavior: Clip.none, | ||||||
|               children: [ |               children: [ | ||||||
|                 buildUserProfileImage(), |                 buildUserProfileImage(), | ||||||
|                 Positioned( |                 Positioned( | ||||||
|                   bottom: 0, |                   bottom: -5, | ||||||
|                   right: -5, |                   right: -8, | ||||||
|                   child: Material( |                   child: Material( | ||||||
|                     color: isDarkMode ? Colors.grey[700] : Colors.grey[100], |                     color: isDarkMode ? Colors.blueGrey[800] : Colors.white, | ||||||
|                     elevation: 3, |                     elevation: 3, | ||||||
|                     shape: RoundedRectangleBorder( |                     shape: RoundedRectangleBorder( | ||||||
|                       borderRadius: BorderRadius.circular(50.0), |                       borderRadius: BorderRadius.circular(50.0), | ||||||
| @@ -135,7 +119,7 @@ class ProfileDrawerHeader extends HookConsumerWidget { | |||||||
|                     child: Padding( |                     child: Padding( | ||||||
|                       padding: const EdgeInsets.all(5.0), |                       padding: const EdgeInsets.all(5.0), | ||||||
|                       child: Icon( |                       child: Icon( | ||||||
|                         Icons.edit, |                         Icons.camera_alt_outlined, | ||||||
|                         color: Theme.of(context).primaryColor, |                         color: Theme.of(context).primaryColor, | ||||||
|                         size: 14, |                         size: 14, | ||||||
|                       ), |                       ), | ||||||
| @@ -145,19 +129,21 @@ class ProfileDrawerHeader extends HookConsumerWidget { | |||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           Text( |           title: Text( | ||||||
|             "${authState.firstName} ${authState.lastName}", |             "${authState.firstName} ${authState.lastName}", | ||||||
|             style: TextStyle( |             style: TextStyle( | ||||||
|               color: Theme.of(context).primaryColor, |               color: Theme.of(context).primaryColor, | ||||||
|               fontWeight: FontWeight.bold, |               fontWeight: FontWeight.bold, | ||||||
|               fontSize: 24, |               fontSize: 16, | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           Text( |           subtitle: Text( | ||||||
|             authState.userEmail, |             authState.userEmail, | ||||||
|             style: Theme.of(context).textTheme.labelMedium, |             style: Theme.of(context).textTheme.labelMedium?.copyWith( | ||||||
|  |                   fontSize: 12, | ||||||
|  |                 ), | ||||||
|  |           ), | ||||||
|         ), |         ), | ||||||
|         ], |  | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
							
								
								
									
										209
									
								
								mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								mobile/lib/shared/ui/app_bar_dialog/app_bar_server_info.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart' hide Store; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/server_info/server_info.model.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/server_info.provider.dart'; | ||||||
|  | import 'package:immich_mobile/utils/url_helper.dart'; | ||||||
|  | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
|  |  | ||||||
|  | class AppBarServerInfo extends HookConsumerWidget { | ||||||
|  |   const AppBarServerInfo({ | ||||||
|  |     Key? key, | ||||||
|  |   }) : super(key: key); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     ServerInfo serverInfoState = ref.watch(serverInfoProvider); | ||||||
|  |  | ||||||
|  |     final appInfo = useState({}); | ||||||
|  |  | ||||||
|  |     getPackageInfo() async { | ||||||
|  |       PackageInfo packageInfo = await PackageInfo.fromPlatform(); | ||||||
|  |  | ||||||
|  |       appInfo.value = { | ||||||
|  |         "version": packageInfo.version, | ||||||
|  |         "buildNumber": packageInfo.buildNumber, | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     useEffect( | ||||||
|  |       () { | ||||||
|  |         getPackageInfo(); | ||||||
|  |         return null; | ||||||
|  |       }, | ||||||
|  |       [], | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return Padding( | ||||||
|  |       padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0), | ||||||
|  |       child: Container( | ||||||
|  |         decoration: BoxDecoration( | ||||||
|  |           color: Theme.of(context).brightness == Brightness.dark | ||||||
|  |               ? Theme.of(context).scaffoldBackgroundColor | ||||||
|  |               : const Color.fromARGB(255, 225, 229, 240), | ||||||
|  |           borderRadius: const BorderRadius.only( | ||||||
|  |             bottomLeft: Radius.circular(10), | ||||||
|  |             bottomRight: Radius.circular(10), | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         child: Padding( | ||||||
|  |           padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8), | ||||||
|  |           child: Column( | ||||||
|  |             crossAxisAlignment: CrossAxisAlignment.center, | ||||||
|  |             children: [ | ||||||
|  |               Padding( | ||||||
|  |                 padding: const EdgeInsets.all(8.0), | ||||||
|  |                 child: Text( | ||||||
|  |                   serverInfoState.isVersionMismatch | ||||||
|  |                       ? serverInfoState.versionMismatchErrorMessage | ||||||
|  |                       : "profile_drawer_client_server_up_to_date".tr(), | ||||||
|  |                   textAlign: TextAlign.center, | ||||||
|  |                   style: TextStyle( | ||||||
|  |                     fontSize: 11, | ||||||
|  |                     color: Theme.of(context).primaryColor, | ||||||
|  |                     fontWeight: FontWeight.w600, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               const Padding( | ||||||
|  |                 padding: EdgeInsets.symmetric(horizontal: 10), | ||||||
|  |                 child: Divider( | ||||||
|  |                   color: Color.fromARGB(101, 201, 201, 201), | ||||||
|  |                   thickness: 1, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               Row( | ||||||
|  |                 mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||||
|  |                 children: [ | ||||||
|  |                   Expanded( | ||||||
|  |                     child: Padding( | ||||||
|  |                       padding: const EdgeInsets.only(left: 10.0), | ||||||
|  |                       child: Text( | ||||||
|  |                         "server_info_box_app_version".tr(), | ||||||
|  |                         style: TextStyle( | ||||||
|  |                           fontSize: 11, | ||||||
|  |                           color: Theme.of(context).textTheme.labelSmall?.color, | ||||||
|  |                           fontWeight: FontWeight.bold, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   Expanded( | ||||||
|  |                     flex: 0, | ||||||
|  |                     child: Padding( | ||||||
|  |                       padding: const EdgeInsets.only(right: 10.0), | ||||||
|  |                       child: Text( | ||||||
|  |                         "${appInfo.value["version"]} build.${appInfo.value["buildNumber"]}", | ||||||
|  |                         style: TextStyle( | ||||||
|  |                           fontSize: 11, | ||||||
|  |                           color: Theme.of(context) | ||||||
|  |                               .textTheme | ||||||
|  |                               .labelSmall | ||||||
|  |                               ?.color | ||||||
|  |                               ?.withOpacity(0.5), | ||||||
|  |                           fontWeight: FontWeight.bold, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |               const Padding( | ||||||
|  |                 padding: EdgeInsets.symmetric(horizontal: 10), | ||||||
|  |                 child: Divider( | ||||||
|  |                   color: Color.fromARGB(101, 201, 201, 201), | ||||||
|  |                   thickness: 1, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               Row( | ||||||
|  |                 mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||||
|  |                 children: [ | ||||||
|  |                   Expanded( | ||||||
|  |                     child: Padding( | ||||||
|  |                       padding: const EdgeInsets.only(left: 10.0), | ||||||
|  |                       child: Text( | ||||||
|  |                         "server_info_box_server_version".tr(), | ||||||
|  |                         style: TextStyle( | ||||||
|  |                           fontSize: 11, | ||||||
|  |                           color: Theme.of(context).textTheme.labelSmall?.color, | ||||||
|  |                           fontWeight: FontWeight.bold, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   Expanded( | ||||||
|  |                     flex: 0, | ||||||
|  |                     child: Padding( | ||||||
|  |                       padding: const EdgeInsets.only(right: 10.0), | ||||||
|  |                       child: Text( | ||||||
|  |                         serverInfoState.serverVersion.major > 0 | ||||||
|  |                             ? "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}" | ||||||
|  |                             : "?", | ||||||
|  |                         style: TextStyle( | ||||||
|  |                           fontSize: 11, | ||||||
|  |                           color: Theme.of(context) | ||||||
|  |                               .textTheme | ||||||
|  |                               .labelSmall | ||||||
|  |                               ?.color | ||||||
|  |                               ?.withOpacity(0.5), | ||||||
|  |                           fontWeight: FontWeight.bold, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |               const Padding( | ||||||
|  |                 padding: EdgeInsets.symmetric(horizontal: 10), | ||||||
|  |                 child: Divider( | ||||||
|  |                   color: Color.fromARGB(101, 201, 201, 201), | ||||||
|  |                   thickness: 1, | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               Row( | ||||||
|  |                 mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||||
|  |                 children: [ | ||||||
|  |                   Expanded( | ||||||
|  |                     child: Padding( | ||||||
|  |                       padding: const EdgeInsets.only(left: 10.0), | ||||||
|  |                       child: Text( | ||||||
|  |                         "server_info_box_server_url".tr(), | ||||||
|  |                         style: TextStyle( | ||||||
|  |                           fontSize: 11, | ||||||
|  |                           color: Theme.of(context).textTheme.labelSmall?.color, | ||||||
|  |                           fontWeight: FontWeight.bold, | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                   Expanded( | ||||||
|  |                     flex: 0, | ||||||
|  |                     child: Container( | ||||||
|  |                       width: 200, | ||||||
|  |                       padding: const EdgeInsets.only(right: 10.0), | ||||||
|  |                       child: Text( | ||||||
|  |                         getServerUrl() ?? '--', | ||||||
|  |                         style: TextStyle( | ||||||
|  |                           fontSize: 11, | ||||||
|  |                           color: Theme.of(context) | ||||||
|  |                               .textTheme | ||||||
|  |                               .labelSmall | ||||||
|  |                               ?.color | ||||||
|  |                               ?.withOpacity(0.5), | ||||||
|  |                           fontWeight: FontWeight.bold, | ||||||
|  |                           overflow: TextOverflow.ellipsis, | ||||||
|  |                         ), | ||||||
|  |                         textAlign: TextAlign.end, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										192
									
								
								mobile/lib/shared/ui/immich_app_bar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								mobile/lib/shared/ui/immich_app_bar.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | |||||||
|  | import 'package:auto_route/auto_route.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/store.dart'; | ||||||
|  | import 'package:immich_mobile/shared/ui/app_bar_dialog/app_bar_dialog.dart'; | ||||||
|  | import 'package:immich_mobile/shared/ui/user_circle_avatar.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'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/server_info/server_info.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/server_info.provider.dart'; | ||||||
|  |  | ||||||
|  | class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { | ||||||
|  |   @override | ||||||
|  |   Size get preferredSize => const Size.fromHeight(kToolbarHeight); | ||||||
|  |   final Widget? action; | ||||||
|  |  | ||||||
|  |   const ImmichAppBar({super.key, this.action}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final BackUpState backupState = ref.watch(backupProvider); | ||||||
|  |     final bool isEnableAutoBackup = | ||||||
|  |         backupState.backgroundBackup || backupState.autoBackup; | ||||||
|  |     final ServerInfo serverInfoState = ref.watch(serverInfoProvider); | ||||||
|  |     AuthenticationState authState = ref.watch(authenticationProvider); | ||||||
|  |     final user = Store.tryGet(StoreKey.currentUser); | ||||||
|  |     final isDarkMode = Theme.of(context).brightness == Brightness.dark; | ||||||
|  |     const widgetSize = 30.0; | ||||||
|  |  | ||||||
|  |     buildProfilePhoto() { | ||||||
|  |       return InkWell( | ||||||
|  |         onTap: () => showDialog( | ||||||
|  |           context: context, | ||||||
|  |           useRootNavigator: false, | ||||||
|  |           builder: (ctx) => const ImmichAppBarDialog(), | ||||||
|  |         ), | ||||||
|  |         borderRadius: BorderRadius.circular(12), | ||||||
|  |         child: authState.profileImagePath.isEmpty || user == null | ||||||
|  |             ? const Icon( | ||||||
|  |                 Icons.face_outlined, | ||||||
|  |                 size: widgetSize, | ||||||
|  |               ) | ||||||
|  |             : UserCircleAvatar( | ||||||
|  |                 radius: 15, | ||||||
|  |                 size: 27, | ||||||
|  |                 user: user, | ||||||
|  |               ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildProfileIndicator() { | ||||||
|  |       return Badge( | ||||||
|  |         label: Container( | ||||||
|  |           decoration: BoxDecoration( | ||||||
|  |             color: Colors.black, | ||||||
|  |             borderRadius: BorderRadius.circular(widgetSize / 2), | ||||||
|  |           ), | ||||||
|  |           child: const Icon( | ||||||
|  |             Icons.info, | ||||||
|  |             color: Color.fromARGB(255, 243, 188, 106), | ||||||
|  |             size: widgetSize / 2, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         backgroundColor: Colors.transparent, | ||||||
|  |         alignment: Alignment.bottomRight, | ||||||
|  |         isLabelVisible: serverInfoState.isVersionMismatch, | ||||||
|  |         offset: const Offset(2, 2), | ||||||
|  |         child: buildProfilePhoto(), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getBackupBadgeIcon() { | ||||||
|  |       final iconColor = isDarkMode ? Colors.white : Colors.black; | ||||||
|  |  | ||||||
|  |       if (isEnableAutoBackup) { | ||||||
|  |         if (backupState.backupProgress == BackUpProgressEnum.inProgress) { | ||||||
|  |           return Container( | ||||||
|  |             padding: const EdgeInsets.all(3.5), | ||||||
|  |             child: CircularProgressIndicator( | ||||||
|  |               strokeWidth: 2, | ||||||
|  |               strokeCap: StrokeCap.round, | ||||||
|  |               valueColor: AlwaysStoppedAnimation<Color>(iconColor), | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         } else if (backupState.backupProgress != | ||||||
|  |                 BackUpProgressEnum.inBackground && | ||||||
|  |             backupState.backupProgress != BackUpProgressEnum.manualInProgress) { | ||||||
|  |           return Icon( | ||||||
|  |             Icons.check_outlined, | ||||||
|  |             size: 9, | ||||||
|  |             color: iconColor, | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (!isEnableAutoBackup) { | ||||||
|  |         return Icon( | ||||||
|  |           Icons.cloud_off_rounded, | ||||||
|  |           size: 9, | ||||||
|  |           color: iconColor, | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     buildBackupIndicator() { | ||||||
|  |       final indicatorIcon = getBackupBadgeIcon(); | ||||||
|  |       final badgeBackground = isDarkMode ? Colors.blueGrey[800] : Colors.white; | ||||||
|  |  | ||||||
|  |       return InkWell( | ||||||
|  |         onTap: () => AutoRouter.of(context).push(const BackupControllerRoute()), | ||||||
|  |         borderRadius: BorderRadius.circular(12), | ||||||
|  |         child: Badge( | ||||||
|  |           label: Container( | ||||||
|  |             width: widgetSize / 2, | ||||||
|  |             height: widgetSize / 2, | ||||||
|  |             decoration: BoxDecoration( | ||||||
|  |               color: badgeBackground, | ||||||
|  |               border: Border.all( | ||||||
|  |                 color: isDarkMode ? Colors.black : Colors.grey, | ||||||
|  |               ), | ||||||
|  |               borderRadius: BorderRadius.circular(widgetSize / 2), | ||||||
|  |             ), | ||||||
|  |             child: indicatorIcon, | ||||||
|  |           ), | ||||||
|  |           backgroundColor: Colors.transparent, | ||||||
|  |           alignment: Alignment.bottomRight, | ||||||
|  |           isLabelVisible: indicatorIcon != null, | ||||||
|  |           offset: const Offset(2, 2), | ||||||
|  |           child: Icon( | ||||||
|  |             Icons.backup_rounded, | ||||||
|  |             size: widgetSize, | ||||||
|  |             color: Theme.of(context).primaryColor, | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return AppBar( | ||||||
|  |       backgroundColor: Theme.of(context).appBarTheme.backgroundColor, | ||||||
|  |       shape: const RoundedRectangleBorder( | ||||||
|  |         borderRadius: BorderRadius.all( | ||||||
|  |           Radius.circular(5), | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |       automaticallyImplyLeading: false, | ||||||
|  |       centerTitle: false, | ||||||
|  |       title: Builder( | ||||||
|  |         builder: (BuildContext context) { | ||||||
|  |           return Row( | ||||||
|  |             children: [ | ||||||
|  |               Container( | ||||||
|  |                 padding: const EdgeInsets.only(top: 3), | ||||||
|  |                 width: 28, | ||||||
|  |                 height: 28, | ||||||
|  |                 child: Image.asset( | ||||||
|  |                   'assets/immich-logo.png', | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |               Container( | ||||||
|  |                 margin: const EdgeInsets.only(left: 10), | ||||||
|  |                 child: const Text( | ||||||
|  |                   'IMMICH', | ||||||
|  |                   style: TextStyle( | ||||||
|  |                     fontFamily: 'SnowburstOne', | ||||||
|  |                     fontWeight: FontWeight.bold, | ||||||
|  |                     fontSize: 24, | ||||||
|  |                   ), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |             ], | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |       ), | ||||||
|  |       actions: [ | ||||||
|  |         if (action != null) | ||||||
|  |           Padding(padding: const EdgeInsets.only(right: 20), child: action!), | ||||||
|  |         Padding( | ||||||
|  |           padding: const EdgeInsets.only(right: 20), | ||||||
|  |           child: buildBackupIndicator(), | ||||||
|  |         ), | ||||||
|  |         Padding( | ||||||
|  |           padding: const EdgeInsets.only(right: 20), | ||||||
|  |           child: buildProfileIndicator(), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,8 +1,11 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
| class ImmichLoadingIndicator extends StatelessWidget { | class ImmichLoadingIndicator extends StatelessWidget { | ||||||
|  |   final double? borderRadius; | ||||||
|  |  | ||||||
|   const ImmichLoadingIndicator({ |   const ImmichLoadingIndicator({ | ||||||
|     Key? key, |     Key? key, | ||||||
|  |     this.borderRadius, | ||||||
|   }) : super(key: key); |   }) : super(key: key); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -12,7 +15,7 @@ class ImmichLoadingIndicator extends StatelessWidget { | |||||||
|       width: 60, |       width: 60, | ||||||
|       decoration: BoxDecoration( |       decoration: BoxDecoration( | ||||||
|         color: Theme.of(context).primaryColor.withAlpha(200), |         color: Theme.of(context).primaryColor.withAlpha(200), | ||||||
|         borderRadius: BorderRadius.circular(10), |         borderRadius: BorderRadius.circular(borderRadius ?? 10), | ||||||
|       ), |       ), | ||||||
|       padding: const EdgeInsets.all(15), |       padding: const EdgeInsets.all(15), | ||||||
|       child: const CircularProgressIndicator( |       child: const CircularProgressIndicator( | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import 'dart:math'; | import 'dart:math'; | ||||||
|  |  | ||||||
|  | import 'package:cached_network_image/cached_network_image.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:immich_mobile/shared/models/store.dart'; | import 'package:immich_mobile/shared/models/store.dart'; | ||||||
| @@ -46,7 +47,7 @@ class UserCircleAvatar extends ConsumerWidget { | |||||||
|       radius: radius, |       radius: radius, | ||||||
|       child: user.profileImagePath == "" |       child: user.profileImagePath == "" | ||||||
|           ? Text( |           ? Text( | ||||||
|               user.firstName[0], |               user.firstName[0].toUpperCase(), | ||||||
|               style: const TextStyle( |               style: const TextStyle( | ||||||
|                 fontWeight: FontWeight.bold, |                 fontWeight: FontWeight.bold, | ||||||
|                 color: Colors.black, |                 color: Colors.black, | ||||||
| @@ -54,19 +55,18 @@ class UserCircleAvatar extends ConsumerWidget { | |||||||
|             ) |             ) | ||||||
|           : ClipRRect( |           : ClipRRect( | ||||||
|               borderRadius: BorderRadius.circular(50), |               borderRadius: BorderRadius.circular(50), | ||||||
|               child: FadeInImage( |               child: CachedNetworkImage( | ||||||
|                 fit: BoxFit.cover, |                 fit: BoxFit.cover, | ||||||
|                 placeholder: MemoryImage(kTransparentImage), |                 cacheKey: user.profileImagePath, | ||||||
|                 width: size, |                 width: size, | ||||||
|                 height: size, |                 height: size, | ||||||
|                 image: NetworkImage( |                 placeholder: (_, __) => Image.memory(kTransparentImage), | ||||||
|                   profileImageUrl, |                 imageUrl: profileImageUrl, | ||||||
|                   headers: { |                 httpHeaders: { | ||||||
|                   "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", |                   "Authorization": "Bearer ${Store.get(StoreKey.accessToken)}", | ||||||
|                 }, |                 }, | ||||||
|                 ), |                 fadeInDuration: const Duration(milliseconds: 300), | ||||||
|                 fadeInDuration: const Duration(milliseconds: 200), |                 errorWidget: (context, error, stackTrace) => | ||||||
|                 imageErrorBuilder: (context, error, stackTrace) => |  | ||||||
|                     Image.memory(kTransparentImage), |                     Image.memory(kTransparentImage), | ||||||
|               ), |               ), | ||||||
|             ), |             ), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user