1
0
mirror of https://github.com/immich-app/immich.git synced 2024-11-24 08:52:28 +02: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:immich_mobile/shared/ui/scaffold_error_body.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
extension ScaffoldBody<T> on AsyncValue<T> { extension LogOnError<T> on AsyncValue<T> {
static final Logger _scaffoldBodyLog = Logger("ScaffoldBody"); 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, required Widget Function(T data) onData,
Widget? onError,
}) { }) {
if (isLoading) { if (isLoading) {
return const Center( bool skip = false;
child: ImmichLoadingIndicator(), if (isRefreshing) {
); skip = skipLoadingOnRefresh;
}
if (!skip) {
return onLoading?.call() ??
const Center(
child: ImmichLoadingIndicator(),
);
}
} }
if (hasError && !hasValue) { if (hasError && !hasValue) {
_scaffoldBodyLog.severe("Error occured in AsyncValue", error, stackTrace); _asyncErrorLogger.severe("Error occured", error, stackTrace);
return onError ?? const ScaffoldErrorBody(); return onError?.call(error, stackTrace) ?? const ScaffoldErrorBody();
} }
return onData(requireValue); return onData(requireValue);

View File

@ -4,12 +4,12 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; 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:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_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/models/activity.model.dart';
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart'; import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
import 'package:immich_mobile/shared/models/store.dart'; import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/confirm_dialog.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/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart'; import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/image_url_builder.dart';
@ -88,7 +88,7 @@ class ActivitiesPage extends HookConsumerWidget {
width: 40, width: 40,
height: 30, height: 30,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage( image: DecorationImage(
image: CachedNetworkImageProvider( image: CachedNetworkImageProvider(
getThumbnailUrlForRemoteId( getThumbnailUrlForRemoteId(
@ -231,11 +231,8 @@ class ActivitiesPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(appBarTitle)), appBar: AppBar(title: Text(appBarTitle)),
body: activities.maybeWhen( body: activities.widgetWhen(
orElse: () { onData: (data) {
return const Center(child: ImmichLoadingIndicator());
},
data: (data) {
final liked = data.firstWhereOrNull( final liked = data.firstWhereOrNull(
(a) => (a) =>
a.type == ActivityType.like && a.type == ActivityType.like &&

View File

@ -180,9 +180,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded), icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () { onPressed: () => context.autoPop(null),
context.autoPop(null);
},
), ),
centerTitle: true, centerTitle: true,
title: Text("translated_text_options".tr()), 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/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.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'; 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/album.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.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/ui/user_circle_avatar.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.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")), error: (error, stackTrace) => AppBar(title: const Text("Error")),
loading: () => AppBar(), loading: () => AppBar(),
), ),
body: album.when( body: album.widgetWhen(
data: (data) => WillPopScope( onData: (data) => WillPopScope(
onWillPop: onWillPop, onWillPop: onWillPop,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () => titleFocusNode.unfocus(),
titleFocusNode.unfocus();
},
child: ImmichAssetGrid( child: ImmichAssetGrid(
renderList: data.renderList, renderList: data.renderList,
listener: selectionListener, 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/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.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'; import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart';
@ -85,12 +86,8 @@ class AssetSelectionPage extends HookConsumerWidget {
), ),
], ],
), ),
body: renderList.when( body: renderList.widgetWhen(
data: (data) => buildBody(data), onData: (data) => buildBody(data),
error: (error, stackTrace) => Center(
child: Text(error.toString()),
),
loading: () => const Center(child: CircularProgressIndicator()),
), ),
); );
} }

View File

@ -2,11 +2,11 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.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/album.dart';
import 'package:immich_mobile/shared/models/user.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'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class SelectAdditionalUserForSharingPage extends HookConsumerWidget { class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
@ -137,8 +137,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
), ),
], ],
), ),
body: suggestedShareUsers.when( body: suggestedShareUsers.widgetWhen(
data: (users) { onData: (users) {
for (var sharedUsers in album.sharedUsers) { for (var sharedUsers in album.sharedUsers) {
users.removeWhere( users.removeWhere(
(u) => u.id == sharedUsers.id || u.id == album.ownerId, (u) => u.id == sharedUsers.id || u.id == album.ownerId,
@ -147,10 +147,6 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
return buildUserList(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/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/providers/album_title.provider.dart'; import 'package:immich_mobile/modules/album/providers/album_title.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.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/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart'; import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/user.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'; import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class SelectUserForSharingPage extends HookConsumerWidget { class SelectUserForSharingPage extends HookConsumerWidget {
@ -42,7 +42,12 @@ class SelectUserForSharingPage extends HookConsumerWidget {
ScaffoldMessenger( ScaffoldMessenger(
child: SnackBar( 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( body: suggestedShareUsers.widgetWhen(
data: (users) { onData: (users) {
return buildUserList(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/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/archive/providers/archive_asset_provider.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'; import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
@ -48,37 +49,33 @@ class ArchivePage extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
height: 64, height: 64,
child: Card( child: Card(
child: Column( child: ListTile(
children: [ shape: const RoundedRectangleBorder(
ListTile( borderRadius: BorderRadius.all(Radius.circular(10)),
shape: RoundedRectangleBorder( ),
borderRadius: BorderRadius.circular(10), leading: const Icon(
), Icons.unarchive_rounded,
leading: const Icon( ),
Icons.unarchive_rounded, title: Text(
), 'control_bottom_app_bar_unarchive'.tr(),
title: Text( style: const TextStyle(fontSize: 14),
'control_bottom_app_bar_unarchive'.tr(), ),
style: const TextStyle(fontSize: 14), onTap: processing.value
), ? null
onTap: processing.value : () async {
? null processing.value = true;
: () async { try {
processing.value = true; await handleArchiveAssets(
try { ref,
await handleArchiveAssets( context,
ref, selection.value.toList(),
context, shouldArchive: false,
selection.value.toList(), );
shouldArchive: false, } finally {
); processing.value = false;
} finally { selectionEnabledHook.value = false;
processing.value = false; }
selectionEnabledHook.value = false; },
}
},
),
],
), ),
), ),
), ),
@ -86,18 +83,13 @@ class ArchivePage extends HookConsumerWidget {
); );
} }
return archivedAssets.when( return Scaffold(
loading: () => Scaffold( appBar: archivedAssets.maybeWhen(
appBar: buildAppBar("?"), data: (data) => buildAppBar(data.totalAssets.toString()),
body: const Center(child: CircularProgressIndicator()), orElse: () => buildAppBar("?"),
), ),
error: (error, stackTrace) => Scaffold( body: archivedAssets.widgetWhen(
appBar: buildAppBar("Error"), onData: (data) => data.isEmpty
body: Center(child: Text(error.toString())),
),
data: (data) => Scaffold(
appBar: buildAppBar(data.totalAssets.toString()),
body: data.isEmpty
? Center( ? Center(
child: Text('archive_page_no_archived_assets'.tr()), child: Text('archive_page_no_archived_assets'.tr()),
) )

View File

@ -62,8 +62,14 @@ class AdvancedBottomSheet extends HookConsumerWidget {
ClipboardData(text: assetDetail.toString()), ClipboardData(text: assetDetail.toString()),
).then((_) { ).then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( SnackBar(
content: Text("Copied to clipboard"), 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( final snackBar = SnackBar(
content: Text( content: Text(
msg.tr(), msg.tr(),
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
),
), ),
backgroundColor: Colors.red, backgroundColor: Colors.red,
); );

View File

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart'; import 'package:immich_mobile/modules/favorite/providers/favorite_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';
@ -62,22 +63,18 @@ class FavoritesPage extends HookConsumerWidget {
child: SizedBox( child: SizedBox(
height: 64, height: 64,
child: Card( child: Card(
child: Column( child: ListTile(
children: [ shape: const RoundedRectangleBorder(
ListTile( borderRadius: BorderRadius.all(Radius.circular(10)),
shape: RoundedRectangleBorder( ),
borderRadius: BorderRadius.circular(10), leading: const Icon(
), Icons.star_border,
leading: const Icon( ),
Icons.star_border, title: const Text(
), "Unfavorite",
title: const Text( style: TextStyle(fontSize: 14),
"Unfavorite", ),
style: TextStyle(fontSize: 14), onTap: processing.value ? null : unfavorite,
),
onTap: processing.value ? null : unfavorite,
),
],
), ),
), ),
), ),
@ -87,10 +84,8 @@ class FavoritesPage extends HookConsumerWidget {
return Scaffold( return Scaffold(
appBar: buildAppBar(), appBar: buildAppBar(),
body: ref.watch(favoriteAssetsProvider).when( body: ref.watch(favoriteAssetsProvider).widgetWhen(
loading: () => const Center(child: CircularProgressIndicator()), onData: (data) => data.isEmpty
error: (error, stackTrace) => Center(child: Text(error.toString())),
data: (data) => data.isEmpty
? Center( ? Center(
child: Text('favorites_page_no_favorites'.tr()), 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/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/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/asset_grid_data_structure.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid_view.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/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/models/asset.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'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class ImmichAssetGrid extends HookConsumerWidget { class ImmichAssetGrid extends HookConsumerWidget {
@ -130,12 +130,8 @@ class ImmichAssetGrid extends HookConsumerWidget {
if (renderList != null) return buildAssetGridView(renderList!); if (renderList != null) return buildAssetGridView(renderList!);
final renderListFuture = ref.watch(renderListProvider(assets!)); final renderListFuture = ref.watch(renderListProvider(assets!));
return renderListFuture.when( return renderListFuture.widgetWhen(
data: (renderList) => buildAssetGridView(renderList), onData: (renderList) => buildAssetGridView(renderList),
error: (err, stack) => Center(child: Text("$err")),
loading: () => const Center(
child: ImmichLoadingIndicator(),
),
); );
} }
} }

View File

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/partner/providers/partner.provider.dart'; import 'package:immich_mobile/modules/partner/providers/partner.provider.dart';
import 'package:immich_mobile/shared/models/user.dart'; import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/providers/asset.provider.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'; import 'package:immich_mobile/shared/ui/immich_toast.dart';
class PartnerDetailPage extends HookConsumerWidget { class PartnerDetailPage extends HookConsumerWidget {
@ -71,8 +71,8 @@ class PartnerDetailPage extends HookConsumerWidget {
), ),
], ],
), ),
body: assets.when( body: assets.widgetWhen(
data: (renderList) => renderList.isEmpty onData: (renderList) => renderList.isEmpty
? Padding( ? Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text( child: Text(
@ -84,8 +84,6 @@ class PartnerDetailPage extends HookConsumerWidget {
onRefresh: () => onRefresh: () =>
ref.read(assetProvider.notifier).getPartnerAssets(partner), 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:easy_localization/easy_localization.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/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_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/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/all_motion_photos.provider.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 { class AllMotionPhotosPage extends HookConsumerWidget {
const AllMotionPhotosPage({super.key}); const AllMotionPhotosPage({super.key});
@ -21,14 +21,10 @@ class AllMotionPhotosPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: motionPhotos.when( body: motionPhotos.widgetWhen(
data: (assets) => ImmichAssetGrid( onData: (assets) => ImmichAssetGrid(
assets: assets, 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:easy_localization/easy_localization.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/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_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/providers/people.provider.dart';
import 'package:immich_mobile/modules/search/ui/explore_grid.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 { class AllPeoplePage extends HookConsumerWidget {
const AllPeoplePage({super.key}); const AllPeoplePage({super.key});
@ -23,12 +23,8 @@ class AllPeoplePage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: curatedPeople.when( body: curatedPeople.widgetWhen(
loading: () => const Center(child: ImmichLoadingIndicator()), onData: (people) => ExploreGrid(
error: (err, stack) => Center(
child: Text('Error: $err'),
),
data: (people) => ExploreGrid(
isPeople: true, isPeople: true,
curatedContent: people, curatedContent: people,
), ),

View File

@ -1,10 +1,10 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.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/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_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/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/all_video_assets.provider.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 { class AllVideosPage extends HookConsumerWidget {
const AllVideosPage({super.key}); const AllVideosPage({super.key});
@ -21,14 +21,10 @@ class AllVideosPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: videos.when( body: videos.widgetWhen(
data: (assets) => ImmichAssetGrid( onData: (assets) => ImmichAssetGrid(
assets: assets, 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:easy_localization/easy_localization.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/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_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/models/curated_content.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.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/modules/search/ui/explore_grid.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class CuratedLocationPage extends HookConsumerWidget { class CuratedLocationPage extends HookConsumerWidget {
@ -26,12 +26,8 @@ class CuratedLocationPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: curatedLocation.when( body: curatedLocation.widgetWhen(
loading: () => const Center(child: ImmichLoadingIndicator()), onData: (curatedLocations) => ExploreGrid(
error: (err, stack) => Center(
child: Text('Error: $err'),
),
data: (curatedLocations) => ExploreGrid(
curatedContent: curatedLocations curatedContent: curatedLocations
.map( .map(
(l) => CuratedContent( (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/providers/people.provider.dart';
import 'package:immich_mobile/modules/search/ui/person_name_edit_form.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/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'; import 'package:immich_mobile/utils/image_url_builder.dart';
class PersonResultPage extends HookConsumerWidget { 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( onData: (renderList) => ImmichAssetGrid(
renderList: renderList, renderList: renderList,
topWidget: Padding( 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:easy_localization/easy_localization.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/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_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/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/recently_added.provider.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 { class RecentlyAddedPage extends HookConsumerWidget {
const RecentlyAddedPage({super.key}); const RecentlyAddedPage({super.key});
@ -21,14 +21,10 @@ class RecentlyAddedPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
), ),
body: recents.when( body: recents.widgetWhen(
data: (searchResponse) => ImmichAssetGrid( onData: (searchResponse) => ImmichAssetGrid(
assets: searchResponse, 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/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; 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:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_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/models/curated_content.dart';
import 'package:immich_mobile/modules/search/providers/people.provider.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/modules/search/ui/search_suggestion_list.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.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/ui/immich_loading_indicator.dart'; import 'package:immich_mobile/shared/ui/scaffold_error_body.dart';
// ignore: must_be_immutable // ignore: must_be_immutable
class SearchPage extends HookConsumerWidget { class SearchPage extends HookConsumerWidget {
@ -73,10 +74,9 @@ class SearchPage extends HookConsumerWidget {
buildPeople() { buildPeople() {
return SizedBox( return SizedBox(
height: imageSize, height: imageSize,
child: curatedPeople.when( child: curatedPeople.widgetWhen(
loading: () => const Center(child: ImmichLoadingIndicator()), onError: (error, stack) => const ScaffoldErrorBody(withIcon: false),
error: (err, stack) => Center(child: Text('Error: $err')), onData: (people) => CuratedPeopleRow(
data: (people) => CuratedPeopleRow(
content: people.take(12).toList(), content: people.take(12).toList(),
onTap: (content, index) { onTap: (content, index) {
context.autoPush( context.autoPush(
@ -97,10 +97,9 @@ class SearchPage extends HookConsumerWidget {
buildPlaces() { buildPlaces() {
return SizedBox( return SizedBox(
height: imageSize, height: imageSize,
child: curatedLocation.when( child: curatedLocation.widgetWhen(
loading: () => const Center(child: ImmichLoadingIndicator()), onError: (error, stack) => const ScaffoldErrorBody(withIcon: false),
error: (err, stack) => Center(child: Text('Error: $err')), onData: (locations) => CuratedPlacesRow(
data: (locations) => CuratedPlacesRow(
isMapEnabled: isMapEnabled, isMapEnabled: isMapEnabled,
content: locations content: locations
.map( .map(

View File

@ -46,9 +46,11 @@ class SharedLinkItem extends ConsumerWidget {
} else if (difference.inHours > 0) { } else if (difference.inHours > 0) {
expiresText = "shared_link_expires_hours".plural(difference.inHours); expiresText = "shared_link_expires_hours".plural(difference.inHours);
} else if (difference.inMinutes > 0) { } 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) { } else if (difference.inSeconds > 0) {
expiresText = "shared_link_expires_seconds".plural(difference.inSeconds); expiresText =
"shared_link_expires_seconds".plural(difference.inSeconds);
} }
} }
return Text( return Text(
@ -85,7 +87,12 @@ class SharedLinkItem extends ConsumerWidget {
).then((_) { ).then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( 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), duration: const Duration(seconds: 2),
), ),
); );
@ -162,9 +169,12 @@ class SharedLinkItem extends ConsumerWidget {
Widget buildBottomInfo() { Widget buildBottomInfo() {
return Row( return Row(
children: [ children: [
if (sharedLink.allowUpload) buildInfoChip("shared_link_info_chip_upload".tr()), if (sharedLink.allowUpload)
if (sharedLink.allowDownload) buildInfoChip("shared_link_info_chip_download".tr()), buildInfoChip("shared_link_info_chip_upload".tr()),
if (sharedLink.showMetadata) buildInfoChip("shared_link_info_chip_metadata".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((_) { ).then((_) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( 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), 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/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/shared_link/models/shared_link.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/providers/shared_link.provider.dart';
import 'package:immich_mobile/modules/shared_link/ui/shared_link_item.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 { class SharedLinkPage extends HookConsumerWidget {
const SharedLinkPage({Key? key}) : super(key: key); const SharedLinkPage({Key? key}) : super(key: key);
@ -18,7 +18,10 @@ class SharedLinkPage extends HookConsumerWidget {
useEffect( useEffect(
() { () {
ref.read(sharedLinksStateProvider.notifier).fetchLinks(); 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, centerTitle: false,
), ),
body: SafeArea( body: SafeArea(
child: sharedLinks.when( child: sharedLinks.widgetWhen(
data: (links) => onError: (error, stackTrace) => buildNoShares(),
onData: (links) =>
links.isNotEmpty ? buildSharesList(links) : buildNoShares(), 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:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/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/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart'; import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
@ -229,18 +230,13 @@ class TrashPage extends HookConsumerWidget {
); );
} }
return trashedAssets.when( return Scaffold(
loading: () => Scaffold( appBar: trashedAssets.maybeWhen(
appBar: buildAppBar("?"), orElse: () => buildAppBar("?"),
body: const Center(child: CircularProgressIndicator()), data: (data) => buildAppBar(data.totalAssets.toString()),
), ),
error: (error, stackTrace) => Scaffold( body: trashedAssets.widgetWhen(
appBar: buildAppBar("!"), onData: (data) => data.isEmpty
body: Center(child: Text(error.toString())),
),
data: (data) => Scaffold(
appBar: buildAppBar(data.totalAssets.toString()),
body: data.isEmpty
? Center( ? Center(
child: Text('trash_page_no_assets'.tr()), child: Text('trash_page_no_assets'.tr()),
) )
@ -254,11 +250,9 @@ class TrashPage extends HookConsumerWidget {
showMultiSelectIndicator: false, showMultiSelectIndicator: false,
showStack: true, showStack: true,
topWidget: Padding( topWidget: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.symmetric(
top: 24, horizontal: 12,
bottom: 24, vertical: 24,
left: 12,
right: 12,
), ),
child: const Text( child: const Text(
"trash_page_info", "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 // Error widget to be used in Scaffold when an AsyncError is received
class ScaffoldErrorBody extends StatelessWidget { 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -14,19 +14,22 @@ class ScaffoldErrorBody extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text( Text(
"scaffold_body_error_occured", "scaffold_body_error_occured",
style: style: context.textTheme.displayMedium,
TextStyle(fontSize: 14, fontWeight: FontWeight.bold, height: 3),
textAlign: TextAlign.center, textAlign: TextAlign.center,
).tr(), ).tr(),
Center( if (withIcon)
child: Icon( Center(
icon, child: Padding(
size: 100, padding: const EdgeInsets.only(top: 15),
color: context.themeData.iconTheme.color?.withOpacity(0.5), 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)) Clipboard.setData(ClipboardData(text: stackTrace))
.then((_) { .then((_) {
ScaffoldMessenger.of(context).showSnackBar( 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: () { onPressed: () {
Clipboard.setData(ClipboardData(text: message)).then((_) { Clipboard.setData(ClipboardData(text: message)).then((_) {
ScaffoldMessenger.of(context).showSnackBar( 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,
),
),
),
); );
}); });
}, },