From 513f252a0c4a4bba73d2a6d6a43eacd44767acb5 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Wed, 29 Nov 2023 04:17:29 +0000 Subject: [PATCH] refactor(mobile): log asyncvalue errors (#5327) * refactor: scaffoldwhen to log errors during scaffold body render * refactor: onError and onLoading scaffoldbody * refactor: more scaffold body to custom extension * refactor: add skiploadingonrefresh * Snackbar color --------- Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex Tran --- .../lib/extensions/asyncvalue_extensions.dart | 28 ++++--- .../activities/views/activities_page.dart | 11 +-- .../album/views/album_options_part.dart | 4 +- .../album/views/album_viewer_page.dart | 14 +--- .../album/views/asset_selection_page.dart | 9 +-- ...lect_additional_user_for_sharing_page.dart | 10 +-- .../views/select_user_for_sharing_page.dart | 17 +++-- .../modules/archive/views/archive_page.dart | 76 +++++++++---------- .../ui/advanced_bottom_sheet.dart | 10 ++- .../backup/views/backup_controller_page.dart | 3 + .../favorite/views/favorites_page.dart | 35 ++++----- .../home/ui/asset_grid/immich_asset_grid.dart | 10 +-- .../partner/views/partner_detail_page.dart | 8 +- .../search/views/all_motion_videos_page.dart | 10 +-- .../modules/search/views/all_people_page.dart | 10 +-- .../modules/search/views/all_videos_page.dart | 10 +-- .../search/views/curated_location_page.dart | 10 +-- .../search/views/person_result_page.dart | 4 +- .../search/views/recently_added_page.dart | 10 +-- .../lib/modules/search/views/search_page.dart | 17 ++--- .../shared_link/ui/shared_link_item.dart | 22 ++++-- .../views/shared_link_edit_page.dart | 7 +- .../shared_link/views/shared_link_page.dart | 14 ++-- .../lib/modules/trash/views/trash_page.dart | 26 +++---- mobile/lib/shared/ui/scaffold_error_body.dart | 25 +++--- .../lib/shared/views/app_log_detail_page.dart | 18 ++++- 26 files changed, 203 insertions(+), 215 deletions(-) diff --git a/mobile/lib/extensions/asyncvalue_extensions.dart b/mobile/lib/extensions/asyncvalue_extensions.dart index 2c4725de8b..036881f3c2 100644 --- a/mobile/lib/extensions/asyncvalue_extensions.dart +++ b/mobile/lib/extensions/asyncvalue_extensions.dart @@ -4,22 +4,32 @@ import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/scaffold_error_body.dart'; import 'package:logging/logging.dart'; -extension ScaffoldBody on AsyncValue { - static final Logger _scaffoldBodyLog = Logger("ScaffoldBody"); +extension LogOnError on AsyncValue { + static final Logger _asyncErrorLogger = Logger("AsyncValue"); - Widget scaffoldBodyWhen({ + Widget widgetWhen({ + bool skipLoadingOnRefresh = true, + Widget Function()? onLoading, + Widget Function(Object? error, StackTrace? stack)? onError, required Widget Function(T data) onData, - Widget? onError, }) { if (isLoading) { - return const Center( - child: ImmichLoadingIndicator(), - ); + bool skip = false; + if (isRefreshing) { + skip = skipLoadingOnRefresh; + } + + if (!skip) { + return onLoading?.call() ?? + const Center( + child: ImmichLoadingIndicator(), + ); + } } if (hasError && !hasValue) { - _scaffoldBodyLog.severe("Error occured in AsyncValue", error, stackTrace); - return onError ?? const ScaffoldErrorBody(); + _asyncErrorLogger.severe("Error occured", error, stackTrace); + return onError?.call(error, stackTrace) ?? const ScaffoldErrorBody(); } return onData(requireValue); diff --git a/mobile/lib/modules/activities/views/activities_page.dart b/mobile/lib/modules/activities/views/activities_page.dart index 1cfd48b5bc..f0c68a3491 100644 --- a/mobile/lib/modules/activities/views/activities_page.dart +++ b/mobile/lib/modules/activities/views/activities_page.dart @@ -4,12 +4,12 @@ import 'package:easy_localization/easy_localization.dart'; 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/activities/models/activity.model.dart'; import 'package:immich_mobile/modules/activities/providers/activity.provider.dart'; import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/ui/confirm_dialog.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/extensions/datetime_extensions.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; @@ -88,7 +88,7 @@ class ActivitiesPage extends HookConsumerWidget { width: 40, height: 30, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), + borderRadius: const BorderRadius.all(Radius.circular(4)), image: DecorationImage( image: CachedNetworkImageProvider( getThumbnailUrlForRemoteId( @@ -231,11 +231,8 @@ class ActivitiesPage extends HookConsumerWidget { return Scaffold( appBar: AppBar(title: Text(appBarTitle)), - body: activities.maybeWhen( - orElse: () { - return const Center(child: ImmichLoadingIndicator()); - }, - data: (data) { + body: activities.widgetWhen( + onData: (data) { final liked = data.firstWhereOrNull( (a) => a.type == ActivityType.like && diff --git a/mobile/lib/modules/album/views/album_options_part.dart b/mobile/lib/modules/album/views/album_options_part.dart index 979cec7cd4..492cede6a7 100644 --- a/mobile/lib/modules/album/views/album_options_part.dart +++ b/mobile/lib/modules/album/views/album_options_part.dart @@ -180,9 +180,7 @@ class AlbumOptionsPage extends HookConsumerWidget { appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new_rounded), - onPressed: () { - context.autoPop(null); - }, + onPressed: () => context.autoPop(null), ), centerTitle: true, title: Text("translated_text_options".tr()), diff --git a/mobile/lib/modules/album/views/album_viewer_page.dart b/mobile/lib/modules/album/views/album_viewer_page.dart index f3b9827822..a75be70fd7 100644 --- a/mobile/lib/modules/album/views/album_viewer_page.dart +++ b/mobile/lib/modules/album/views/album_viewer_page.dart @@ -4,6 +4,7 @@ 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; import 'package:immich_mobile/modules/album/providers/album_detail.provider.dart'; @@ -17,7 +18,6 @@ import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; import 'package:immich_mobile/shared/views/immich_loading_overlay.dart'; @@ -260,13 +260,11 @@ class AlbumViewerPage extends HookConsumerWidget { error: (error, stackTrace) => AppBar(title: const Text("Error")), loading: () => AppBar(), ), - body: album.when( - data: (data) => WillPopScope( + body: album.widgetWhen( + onData: (data) => WillPopScope( onWillPop: onWillPop, child: GestureDetector( - onTap: () { - titleFocusNode.unfocus(); - }, + onTap: () => titleFocusNode.unfocus(), child: ImmichAssetGrid( renderList: data.renderList, listener: selectionListener, @@ -285,10 +283,6 @@ class AlbumViewerPage extends HookConsumerWidget { ), ), ), - error: (e, _) => Center(child: Text("Error loading album info!\n$e")), - loading: () => const Center( - child: ImmichLoadingIndicator(), - ), ), ); } diff --git a/mobile/lib/modules/album/views/asset_selection_page.dart b/mobile/lib/modules/album/views/asset_selection_page.dart index c1870fe442..471a74ace5 100644 --- a/mobile/lib/modules/album/views/asset_selection_page.dart +++ b/mobile/lib/modules/album/views/asset_selection_page.dart @@ -3,6 +3,7 @@ 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart'; @@ -85,12 +86,8 @@ class AssetSelectionPage extends HookConsumerWidget { ), ], ), - body: renderList.when( - data: (data) => buildBody(data), - error: (error, stackTrace) => Center( - child: Text(error.toString()), - ), - loading: () => const Center(child: CircularProgressIndicator()), + body: renderList.widgetWhen( + onData: (data) => buildBody(data), ), ); } diff --git a/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart b/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart index b91197d282..2aad67ef56 100644 --- a/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart +++ b/mobile/lib/modules/album/views/select_additional_user_for_sharing_page.dart @@ -2,11 +2,11 @@ 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart'; import 'package:immich_mobile/shared/models/album.dart'; import 'package:immich_mobile/shared/models/user.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; class SelectAdditionalUserForSharingPage extends HookConsumerWidget { @@ -137,8 +137,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { ), ], ), - body: suggestedShareUsers.when( - data: (users) { + body: suggestedShareUsers.widgetWhen( + onData: (users) { for (var sharedUsers in album.sharedUsers) { users.removeWhere( (u) => u.id == sharedUsers.id || u.id == album.ownerId, @@ -147,10 +147,6 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget { return buildUserList(users); }, - error: (e, _) => Text("Error loading suggested users $e"), - loading: () => const Center( - child: ImmichLoadingIndicator(), - ), ), ); } diff --git a/mobile/lib/modules/album/views/select_user_for_sharing_page.dart b/mobile/lib/modules/album/views/select_user_for_sharing_page.dart index 61ced47e22..3d6dcf6787 100644 --- a/mobile/lib/modules/album/views/select_user_for_sharing_page.dart +++ b/mobile/lib/modules/album/views/select_user_for_sharing_page.dart @@ -2,6 +2,7 @@ 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/album/providers/album_title.provider.dart'; import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart'; @@ -9,7 +10,6 @@ import 'package:immich_mobile/modules/album/providers/suggested_shared_users.pro import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/user.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart'; class SelectUserForSharingPage extends HookConsumerWidget { @@ -42,7 +42,12 @@ class SelectUserForSharingPage extends HookConsumerWidget { ScaffoldMessenger( child: SnackBar( - content: const Text('select_user_for_sharing_page_err_album').tr(), + content: Text( + 'select_user_for_sharing_page_err_album', + style: context.textTheme.bodyLarge?.copyWith( + color: context.primaryColor, + ), + ).tr(), ), ); } @@ -166,14 +171,10 @@ class SelectUserForSharingPage extends HookConsumerWidget { ), ], ), - body: suggestedShareUsers.when( - data: (users) { + body: suggestedShareUsers.widgetWhen( + onData: (users) { return buildUserList(users); }, - error: (e, _) => Text("Error loading suggested users $e"), - loading: () => const Center( - child: ImmichLoadingIndicator(), - ), ), ); } diff --git a/mobile/lib/modules/archive/views/archive_page.dart b/mobile/lib/modules/archive/views/archive_page.dart index 06270afc1a..fb3cecc10f 100644 --- a/mobile/lib/modules/archive/views/archive_page.dart +++ b/mobile/lib/modules/archive/views/archive_page.dart @@ -2,6 +2,7 @@ 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/archive/providers/archive_asset_provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; @@ -48,37 +49,33 @@ class ArchivePage extends HookConsumerWidget { child: SizedBox( height: 64, child: Card( - child: Column( - children: [ - ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - leading: const Icon( - Icons.unarchive_rounded, - ), - title: Text( - 'control_bottom_app_bar_unarchive'.tr(), - style: const TextStyle(fontSize: 14), - ), - onTap: processing.value - ? null - : () async { - processing.value = true; - try { - await handleArchiveAssets( - ref, - context, - selection.value.toList(), - shouldArchive: false, - ); - } finally { - processing.value = false; - selectionEnabledHook.value = false; - } - }, - ), - ], + child: ListTile( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + leading: const Icon( + Icons.unarchive_rounded, + ), + title: Text( + 'control_bottom_app_bar_unarchive'.tr(), + style: const TextStyle(fontSize: 14), + ), + onTap: processing.value + ? null + : () async { + processing.value = true; + try { + await handleArchiveAssets( + ref, + context, + selection.value.toList(), + shouldArchive: false, + ); + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + }, ), ), ), @@ -86,18 +83,13 @@ class ArchivePage extends HookConsumerWidget { ); } - return archivedAssets.when( - loading: () => Scaffold( - appBar: buildAppBar("?"), - body: const Center(child: CircularProgressIndicator()), + return Scaffold( + appBar: archivedAssets.maybeWhen( + data: (data) => buildAppBar(data.totalAssets.toString()), + orElse: () => buildAppBar("?"), ), - error: (error, stackTrace) => Scaffold( - appBar: buildAppBar("Error"), - body: Center(child: Text(error.toString())), - ), - data: (data) => Scaffold( - appBar: buildAppBar(data.totalAssets.toString()), - body: data.isEmpty + body: archivedAssets.widgetWhen( + onData: (data) => data.isEmpty ? Center( child: Text('archive_page_no_archived_assets'.tr()), ) diff --git a/mobile/lib/modules/asset_viewer/ui/advanced_bottom_sheet.dart b/mobile/lib/modules/asset_viewer/ui/advanced_bottom_sheet.dart index 97b955b5f3..c265346b00 100644 --- a/mobile/lib/modules/asset_viewer/ui/advanced_bottom_sheet.dart +++ b/mobile/lib/modules/asset_viewer/ui/advanced_bottom_sheet.dart @@ -62,8 +62,14 @@ class AdvancedBottomSheet extends HookConsumerWidget { ClipboardData(text: assetDetail.toString()), ).then((_) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text("Copied to clipboard"), + SnackBar( + content: Text( + "Copied to clipboard", + style: context.textTheme.bodyLarge + ?.copyWith( + color: context.primaryColor, + ), + ), ), ); }); diff --git a/mobile/lib/modules/backup/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart index 9ffdd7aa5a..2bdb3a5dde 100644 --- a/mobile/lib/modules/backup/views/backup_controller_page.dart +++ b/mobile/lib/modules/backup/views/backup_controller_page.dart @@ -229,6 +229,9 @@ class BackupControllerPage extends HookConsumerWidget { final snackBar = SnackBar( content: Text( msg.tr(), + style: context.textTheme.bodyLarge?.copyWith( + color: context.primaryColor, + ), ), backgroundColor: Colors.red, ); diff --git a/mobile/lib/modules/favorite/views/favorites_page.dart b/mobile/lib/modules/favorite/views/favorites_page.dart index 1636113968..095297507b 100644 --- a/mobile/lib/modules/favorite/views/favorites_page.dart +++ b/mobile/lib/modules/favorite/views/favorites_page.dart @@ -2,6 +2,7 @@ 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; @@ -62,22 +63,18 @@ class FavoritesPage extends HookConsumerWidget { child: SizedBox( height: 64, child: Card( - child: Column( - children: [ - ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - leading: const Icon( - Icons.star_border, - ), - title: const Text( - "Unfavorite", - style: TextStyle(fontSize: 14), - ), - onTap: processing.value ? null : unfavorite, - ), - ], + child: ListTile( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + leading: const Icon( + Icons.star_border, + ), + title: const Text( + "Unfavorite", + style: TextStyle(fontSize: 14), + ), + onTap: processing.value ? null : unfavorite, ), ), ), @@ -87,10 +84,8 @@ class FavoritesPage extends HookConsumerWidget { return Scaffold( appBar: buildAppBar(), - body: ref.watch(favoriteAssetsProvider).when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stackTrace) => Center(child: Text(error.toString())), - data: (data) => data.isEmpty + body: ref.watch(favoriteAssetsProvider).widgetWhen( + onData: (data) => data.isEmpty ? Center( child: Text('favorites_page_no_favorites'.tr()), ) diff --git a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart index 2c0f63394b..562b7892c3 100644 --- a/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart +++ b/mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart @@ -5,13 +5,13 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid_view.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/shared/models/asset.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; class ImmichAssetGrid extends HookConsumerWidget { @@ -130,12 +130,8 @@ class ImmichAssetGrid extends HookConsumerWidget { if (renderList != null) return buildAssetGridView(renderList!); final renderListFuture = ref.watch(renderListProvider(assets!)); - return renderListFuture.when( - data: (renderList) => buildAssetGridView(renderList), - error: (err, stack) => Center(child: Text("$err")), - loading: () => const Center( - child: ImmichLoadingIndicator(), - ), + return renderListFuture.widgetWhen( + onData: (renderList) => buildAssetGridView(renderList), ); } } diff --git a/mobile/lib/modules/partner/views/partner_detail_page.dart b/mobile/lib/modules/partner/views/partner_detail_page.dart index f7b1580b20..28d53646df 100644 --- a/mobile/lib/modules/partner/views/partner_detail_page.dart +++ b/mobile/lib/modules/partner/views/partner_detail_page.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/partner/providers/partner.provider.dart'; import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/providers/asset.provider.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/immich_toast.dart'; class PartnerDetailPage extends HookConsumerWidget { @@ -71,8 +71,8 @@ class PartnerDetailPage extends HookConsumerWidget { ), ], ), - body: assets.when( - data: (renderList) => renderList.isEmpty + body: assets.widgetWhen( + onData: (renderList) => renderList.isEmpty ? Padding( padding: const EdgeInsets.all(16), child: Text( @@ -84,8 +84,6 @@ class PartnerDetailPage extends HookConsumerWidget { onRefresh: () => ref.read(assetProvider.notifier).getPartnerAssets(partner), ), - error: (e, _) => Text("Error loading partners:\n$e"), - loading: () => const Center(child: ImmichLoadingIndicator()), ), ); } diff --git a/mobile/lib/modules/search/views/all_motion_videos_page.dart b/mobile/lib/modules/search/views/all_motion_videos_page.dart index 2ad53ec456..8290f0dd6e 100644 --- a/mobile/lib/modules/search/views/all_motion_videos_page.dart +++ b/mobile/lib/modules/search/views/all_motion_videos_page.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/search/providers/all_motion_photos.provider.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; class AllMotionPhotosPage extends HookConsumerWidget { const AllMotionPhotosPage({super.key}); @@ -21,14 +21,10 @@ class AllMotionPhotosPage extends HookConsumerWidget { icon: const Icon(Icons.arrow_back_ios_rounded), ), ), - body: motionPhotos.when( - data: (assets) => ImmichAssetGrid( + body: motionPhotos.widgetWhen( + onData: (assets) => ImmichAssetGrid( assets: assets, ), - error: (e, s) => Text(e.toString()), - loading: () => const Center( - child: ImmichLoadingIndicator(), - ), ), ); } diff --git a/mobile/lib/modules/search/views/all_people_page.dart b/mobile/lib/modules/search/views/all_people_page.dart index 3af087e4e7..7a81831482 100644 --- a/mobile/lib/modules/search/views/all_people_page.dart +++ b/mobile/lib/modules/search/views/all_people_page.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/ui/explore_grid.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; class AllPeoplePage extends HookConsumerWidget { const AllPeoplePage({super.key}); @@ -23,12 +23,8 @@ class AllPeoplePage extends HookConsumerWidget { icon: const Icon(Icons.arrow_back_ios_rounded), ), ), - body: curatedPeople.when( - loading: () => const Center(child: ImmichLoadingIndicator()), - error: (err, stack) => Center( - child: Text('Error: $err'), - ), - data: (people) => ExploreGrid( + body: curatedPeople.widgetWhen( + onData: (people) => ExploreGrid( isPeople: true, curatedContent: people, ), diff --git a/mobile/lib/modules/search/views/all_videos_page.dart b/mobile/lib/modules/search/views/all_videos_page.dart index beb604fd0d..6835398801 100644 --- a/mobile/lib/modules/search/views/all_videos_page.dart +++ b/mobile/lib/modules/search/views/all_videos_page.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/search/providers/all_video_assets.provider.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; class AllVideosPage extends HookConsumerWidget { const AllVideosPage({super.key}); @@ -21,14 +21,10 @@ class AllVideosPage extends HookConsumerWidget { icon: const Icon(Icons.arrow_back_ios_rounded), ), ), - body: videos.when( - data: (assets) => ImmichAssetGrid( + body: videos.widgetWhen( + onData: (assets) => ImmichAssetGrid( assets: assets, ), - error: (e, s) => Text(e.toString()), - loading: () => const Center( - child: ImmichLoadingIndicator(), - ), ), ); } diff --git a/mobile/lib/modules/search/views/curated_location_page.dart b/mobile/lib/modules/search/views/curated_location_page.dart index cb6f8f9ae8..6675e0826f 100644 --- a/mobile/lib/modules/search/views/curated_location_page.dart +++ b/mobile/lib/modules/search/views/curated_location_page.dart @@ -1,11 +1,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart'; import 'package:immich_mobile/modules/search/ui/explore_grid.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; import 'package:openapi/api.dart'; class CuratedLocationPage extends HookConsumerWidget { @@ -26,12 +26,8 @@ class CuratedLocationPage extends HookConsumerWidget { icon: const Icon(Icons.arrow_back_ios_rounded), ), ), - body: curatedLocation.when( - loading: () => const Center(child: ImmichLoadingIndicator()), - error: (err, stack) => Center( - child: Text('Error: $err'), - ), - data: (curatedLocations) => ExploreGrid( + body: curatedLocation.widgetWhen( + onData: (curatedLocations) => ExploreGrid( curatedContent: curatedLocations .map( (l) => CuratedContent( diff --git a/mobile/lib/modules/search/views/person_result_page.dart b/mobile/lib/modules/search/views/person_result_page.dart index 004e3c6578..40a2d1b14b 100644 --- a/mobile/lib/modules/search/views/person_result_page.dart +++ b/mobile/lib/modules/search/views/person_result_page.dart @@ -8,7 +8,6 @@ import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart' import 'package:immich_mobile/modules/search/providers/people.provider.dart'; import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart'; import 'package:immich_mobile/shared/models/store.dart' as isar_store; -import 'package:immich_mobile/shared/ui/scaffold_error_body.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; class PersonResultPage extends HookConsumerWidget { @@ -112,7 +111,7 @@ class PersonResultPage extends HookConsumerWidget { ), ], ), - body: ref.watch(personAssetsProvider(personId)).scaffoldBodyWhen( + body: ref.watch(personAssetsProvider(personId)).widgetWhen( onData: (renderList) => ImmichAssetGrid( renderList: renderList, topWidget: Padding( @@ -137,7 +136,6 @@ class PersonResultPage extends HookConsumerWidget { ), ), ), - onError: const ScaffoldErrorBody(icon: Icons.person_off_outlined), ), ); } diff --git a/mobile/lib/modules/search/views/recently_added_page.dart b/mobile/lib/modules/search/views/recently_added_page.dart index 55e9206152..538dea3d71 100644 --- a/mobile/lib/modules/search/views/recently_added_page.dart +++ b/mobile/lib/modules/search/views/recently_added_page.dart @@ -1,10 +1,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/search/providers/recently_added.provider.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; class RecentlyAddedPage extends HookConsumerWidget { const RecentlyAddedPage({super.key}); @@ -21,14 +21,10 @@ class RecentlyAddedPage extends HookConsumerWidget { icon: const Icon(Icons.arrow_back_ios_rounded), ), ), - body: recents.when( - data: (searchResponse) => ImmichAssetGrid( + body: recents.widgetWhen( + onData: (searchResponse) => ImmichAssetGrid( assets: searchResponse, ), - error: (e, s) => Text(e.toString()), - loading: () => const Center( - child: ImmichLoadingIndicator(), - ), ), ); } diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart index 47ee66dc5e..fb4bd49794 100644 --- a/mobile/lib/modules/search/views/search_page.dart +++ b/mobile/lib/modules/search/views/search_page.dart @@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/providers/people.provider.dart'; @@ -15,7 +16,7 @@ import 'package:immich_mobile/modules/search/ui/search_row_title.dart'; import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/shared/providers/server_info.provider.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; +import 'package:immich_mobile/shared/ui/scaffold_error_body.dart'; // ignore: must_be_immutable class SearchPage extends HookConsumerWidget { @@ -73,10 +74,9 @@ class SearchPage extends HookConsumerWidget { buildPeople() { return SizedBox( height: imageSize, - child: curatedPeople.when( - loading: () => const Center(child: ImmichLoadingIndicator()), - error: (err, stack) => Center(child: Text('Error: $err')), - data: (people) => CuratedPeopleRow( + child: curatedPeople.widgetWhen( + onError: (error, stack) => const ScaffoldErrorBody(withIcon: false), + onData: (people) => CuratedPeopleRow( content: people.take(12).toList(), onTap: (content, index) { context.autoPush( @@ -97,10 +97,9 @@ class SearchPage extends HookConsumerWidget { buildPlaces() { return SizedBox( height: imageSize, - child: curatedLocation.when( - loading: () => const Center(child: ImmichLoadingIndicator()), - error: (err, stack) => Center(child: Text('Error: $err')), - data: (locations) => CuratedPlacesRow( + child: curatedLocation.widgetWhen( + onError: (error, stack) => const ScaffoldErrorBody(withIcon: false), + onData: (locations) => CuratedPlacesRow( isMapEnabled: isMapEnabled, content: locations .map( diff --git a/mobile/lib/modules/shared_link/ui/shared_link_item.dart b/mobile/lib/modules/shared_link/ui/shared_link_item.dart index 56e5d4af5d..8605bdeaf8 100644 --- a/mobile/lib/modules/shared_link/ui/shared_link_item.dart +++ b/mobile/lib/modules/shared_link/ui/shared_link_item.dart @@ -46,9 +46,11 @@ class SharedLinkItem extends ConsumerWidget { } else if (difference.inHours > 0) { expiresText = "shared_link_expires_hours".plural(difference.inHours); } else if (difference.inMinutes > 0) { - expiresText = "shared_link_expires_minutes".plural(difference.inMinutes); + expiresText = + "shared_link_expires_minutes".plural(difference.inMinutes); } else if (difference.inSeconds > 0) { - expiresText = "shared_link_expires_seconds".plural(difference.inSeconds); + expiresText = + "shared_link_expires_seconds".plural(difference.inSeconds); } } return Text( @@ -85,7 +87,12 @@ class SharedLinkItem extends ConsumerWidget { ).then((_) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text("shared_link_clipboard_copied_massage").tr(), + content: Text( + "shared_link_clipboard_copied_massage", + style: context.textTheme.bodyLarge?.copyWith( + color: context.primaryColor, + ), + ).tr(), duration: const Duration(seconds: 2), ), ); @@ -162,9 +169,12 @@ class SharedLinkItem extends ConsumerWidget { Widget buildBottomInfo() { return Row( children: [ - if (sharedLink.allowUpload) buildInfoChip("shared_link_info_chip_upload".tr()), - if (sharedLink.allowDownload) buildInfoChip("shared_link_info_chip_download".tr()), - if (sharedLink.showMetadata) buildInfoChip("shared_link_info_chip_metadata".tr()), + if (sharedLink.allowUpload) + buildInfoChip("shared_link_info_chip_upload".tr()), + if (sharedLink.allowDownload) + buildInfoChip("shared_link_info_chip_download".tr()), + if (sharedLink.showMetadata) + buildInfoChip("shared_link_info_chip_metadata".tr()), ], ); } diff --git a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart index df14ba3d9d..fe2212cc21 100644 --- a/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart +++ b/mobile/lib/modules/shared_link/views/shared_link_edit_page.dart @@ -275,7 +275,12 @@ class SharedLinkEditPage extends HookConsumerWidget { ).then((_) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text("shared_link_clipboard_copied_massage").tr(), + content: Text( + "shared_link_clipboard_copied_massage", + style: context.textTheme.bodyLarge?.copyWith( + color: context.primaryColor, + ), + ).tr(), duration: const Duration(seconds: 2), ), ); diff --git a/mobile/lib/modules/shared_link/views/shared_link_page.dart b/mobile/lib/modules/shared_link/views/shared_link_page.dart index f878f121e3..7638441b17 100644 --- a/mobile/lib/modules/shared_link/views/shared_link_page.dart +++ b/mobile/lib/modules/shared_link/views/shared_link_page.dart @@ -2,11 +2,11 @@ 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/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/shared_link/models/shared_link.dart'; import 'package:immich_mobile/modules/shared_link/providers/shared_link.provider.dart'; import 'package:immich_mobile/modules/shared_link/ui/shared_link_item.dart'; -import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart'; class SharedLinkPage extends HookConsumerWidget { const SharedLinkPage({Key? key}) : super(key: key); @@ -18,7 +18,10 @@ class SharedLinkPage extends HookConsumerWidget { useEffect( () { ref.read(sharedLinksStateProvider.notifier).fetchLinks(); - return () => ref.invalidate(sharedLinksStateProvider); + return () { + if (!context.mounted) return; + ref.invalidate(sharedLinksStateProvider); + }; }, [], ); @@ -113,11 +116,10 @@ class SharedLinkPage extends HookConsumerWidget { centerTitle: false, ), body: SafeArea( - child: sharedLinks.when( - data: (links) => + child: sharedLinks.widgetWhen( + onError: (error, stackTrace) => buildNoShares(), + onData: (links) => links.isNotEmpty ? buildSharesList(links) : buildNoShares(), - error: (error, stackTrace) => buildNoShares(), - loading: () => const Center(child: ImmichLoadingIndicator()), ), ), ); diff --git a/mobile/lib/modules/trash/views/trash_page.dart b/mobile/lib/modules/trash/views/trash_page.dart index ad365189ea..63a18c5a1a 100644 --- a/mobile/lib/modules/trash/views/trash_page.dart +++ b/mobile/lib/modules/trash/views/trash_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart'; import 'package:immich_mobile/modules/home/ui/delete_dialog.dart'; @@ -229,18 +230,13 @@ class TrashPage extends HookConsumerWidget { ); } - return trashedAssets.when( - loading: () => Scaffold( - appBar: buildAppBar("?"), - body: const Center(child: CircularProgressIndicator()), + return Scaffold( + appBar: trashedAssets.maybeWhen( + orElse: () => buildAppBar("?"), + data: (data) => buildAppBar(data.totalAssets.toString()), ), - error: (error, stackTrace) => Scaffold( - appBar: buildAppBar("!"), - body: Center(child: Text(error.toString())), - ), - data: (data) => Scaffold( - appBar: buildAppBar(data.totalAssets.toString()), - body: data.isEmpty + body: trashedAssets.widgetWhen( + onData: (data) => data.isEmpty ? Center( child: Text('trash_page_no_assets'.tr()), ) @@ -254,11 +250,9 @@ class TrashPage extends HookConsumerWidget { showMultiSelectIndicator: false, showStack: true, topWidget: Padding( - padding: const EdgeInsets.only( - top: 24, - bottom: 24, - left: 12, - right: 12, + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 24, ), child: const Text( "trash_page_info", diff --git a/mobile/lib/shared/ui/scaffold_error_body.dart b/mobile/lib/shared/ui/scaffold_error_body.dart index 5c29f7c2a9..fef6bef59e 100644 --- a/mobile/lib/shared/ui/scaffold_error_body.dart +++ b/mobile/lib/shared/ui/scaffold_error_body.dart @@ -4,9 +4,9 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; // Error widget to be used in Scaffold when an AsyncError is received class ScaffoldErrorBody extends StatelessWidget { - final IconData icon; + final bool withIcon; - const ScaffoldErrorBody({this.icon = Icons.error_outline, super.key}); + const ScaffoldErrorBody({super.key, this.withIcon = true}); @override Widget build(BuildContext context) { @@ -14,19 +14,22 @@ class ScaffoldErrorBody extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text( + Text( "scaffold_body_error_occured", - style: - TextStyle(fontSize: 14, fontWeight: FontWeight.bold, height: 3), + style: context.textTheme.displayMedium, textAlign: TextAlign.center, ).tr(), - Center( - child: Icon( - icon, - size: 100, - color: context.themeData.iconTheme.color?.withOpacity(0.5), + if (withIcon) + Center( + child: Padding( + padding: const EdgeInsets.only(top: 15), + child: Icon( + Icons.error_outline, + size: 100, + color: context.themeData.iconTheme.color?.withOpacity(0.5), + ), + ), ), - ), ], ); } diff --git a/mobile/lib/shared/views/app_log_detail_page.dart b/mobile/lib/shared/views/app_log_detail_page.dart index f8ddf51918..8b737fb5cc 100644 --- a/mobile/lib/shared/views/app_log_detail_page.dart +++ b/mobile/lib/shared/views/app_log_detail_page.dart @@ -39,7 +39,14 @@ class AppLogDetailPage extends HookConsumerWidget { Clipboard.setData(ClipboardData(text: stackTrace)) .then((_) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Copied to clipboard")), + SnackBar( + content: Text( + "Copied to clipboard", + style: context.textTheme.bodyLarge?.copyWith( + color: context.primaryColor, + ), + ), + ), ); }); }, @@ -98,7 +105,14 @@ class AppLogDetailPage extends HookConsumerWidget { onPressed: () { Clipboard.setData(ClipboardData(text: message)).then((_) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Copied to clipboard")), + SnackBar( + content: Text( + "Copied to clipboard", + style: context.textTheme.bodyLarge?.copyWith( + color: context.primaryColor, + ), + ), + ), ); }); },