1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +00:00

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 <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2023-11-29 04:17:29 +00:00 committed by GitHub
parent 0fe704c6f9
commit 513f252a0c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 203 additions and 215 deletions

View file

@ -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<T> on AsyncValue<T> {
static final Logger _scaffoldBodyLog = Logger("ScaffoldBody");
extension LogOnError<T> on AsyncValue<T> {
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);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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