mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02: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:
parent
603b056512
commit
9f56bf0ab9
@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Loading…
Reference in New Issue
Block a user