1
0
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:
shenlong 2023-10-30 17:17:34 +00:00 committed by GitHub
parent 603b056512
commit 9f56bf0ab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 781 additions and 624 deletions

View File

@ -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"
} }

View File

@ -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(

View File

@ -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()),

View File

@ -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(),

View File

@ -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),
),
),
],
),
],
);
}
}

View File

@ -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(),
],
),
);
}
}

View File

@ -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,
),
),
],
),
],
),
),
),
);
}
}

View File

@ -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(),
); );
} }

View File

@ -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,

View File

@ -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,
), ),

View File

@ -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,

View 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(),
],
),
),
),
);
}
}

View File

@ -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,
),
),
), ),
],
), ),
); );
} }

View 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,
),
),
),
],
),
],
),
),
),
);
}
}

View 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(),
),
],
);
}
}

View File

@ -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(

View File

@ -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),
), ),
), ),