From 9f6b105cfe495b94611142e1467e5bf0016f493f Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Mon, 23 Oct 2023 11:37:00 +0200 Subject: [PATCH] feat(neon): add ability to display NeonError as a ListTile Signed-off-by: Nikolas Rimikis --- .../neon/lib/src/widgets/account_tile.dart | 2 +- .../neon/neon/lib/src/widgets/app_bar.dart | 2 +- packages/neon/neon/lib/src/widgets/error.dart | 112 +++++++++++------- packages/neon/neon/lib/src/widgets/image.dart | 2 +- 4 files changed, 72 insertions(+), 46 deletions(-) diff --git a/packages/neon/neon/lib/src/widgets/account_tile.dart b/packages/neon/neon/lib/src/widgets/account_tile.dart index 3602f095..421bfa9b 100644 --- a/packages/neon/neon/lib/src/widgets/account_tile.dart +++ b/packages/neon/neon/lib/src/widgets/account_tile.dart @@ -80,7 +80,7 @@ class NeonAccountTile extends StatelessWidget { if (userDetails.hasError) NeonError( userDetails.error, - onlyIcon: true, + type: NeonErrorType.iconOnly, iconSize: 24, onRetry: userDetailsBloc.refresh, ), diff --git a/packages/neon/neon/lib/src/widgets/app_bar.dart b/packages/neon/neon/lib/src/widgets/app_bar.dart index 2ff61ae5..ac06c62c 100644 --- a/packages/neon/neon/lib/src/widgets/app_bar.dart +++ b/packages/neon/neon/lib/src/widgets/app_bar.dart @@ -94,7 +94,7 @@ class _NeonAppBarState extends State { NeonError( appImplementations.error, onRetry: appsBloc.refresh, - onlyIcon: true, + type: NeonErrorType.iconOnly, ), ], if (appImplementations.isLoading) ...[ diff --git a/packages/neon/neon/lib/src/widgets/error.dart b/packages/neon/neon/lib/src/widgets/error.dart index 70fc5947..18c87fd4 100644 --- a/packages/neon/neon/lib/src/widgets/error.dart +++ b/packages/neon/neon/lib/src/widgets/error.dart @@ -11,15 +11,32 @@ import 'package:neon/src/utils/provider.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:universal_io/io.dart'; +/// The display mode of the [NeonError] widget. +enum NeonErrorType { + /// Only shows the error icon. + /// + /// This mode creates an IconButton that is usually used in size constrained + /// areas. + iconOnly, + + /// Shows a column with the error message and a retry button. + column, + + /// Shows a [ListTile] with the error. + listTile, +} + /// An indicator that an [error] has occurred. /// /// The action that lead to the error can be retried. +/// The error can be indicated in various styles by providing [type] which +/// defaults to `NeonErrorType.column`. class NeonError extends StatelessWidget { /// Creates a NeonError. const NeonError( this.error, { required this.onRetry, - this.onlyIcon = false, + this.type = NeonErrorType.column, this.iconSize, this.color, super.key, @@ -33,9 +50,6 @@ class NeonError extends StatelessWidget { /// A function that's called when the user decides to retry the action that lead to the error. final VoidCallback onRetry; - /// Changes whether the text is displayed additionally or not. - final bool onlyIcon; - /// The size of the icon in logical pixels. /// /// Defaults to a size of `30`. @@ -46,6 +60,11 @@ class NeonError extends StatelessWidget { /// Defaults to the nearest [IconTheme]'s [ColorScheme.error]. final Color? color; + /// The display mode of this widget. + /// + /// Defaults to `NeonErrorType.column`. + final NeonErrorType type; + /// Shows a [SnackBar] popup for the [error]. static void showSnackbar(final BuildContext context, final Object? error) { final details = getDetails(error); @@ -71,63 +90,70 @@ class NeonError extends StatelessWidget { final details = getDetails(error); final color = this.color ?? Theme.of(context).colorScheme.error; + final textStyle = TextStyle( + color: color, + ); + final message = details.getText(context); final errorIcon = Icon( Icons.error_outline, size: iconSize ?? 30, color: color, ); - final message = + final actionMessage = details.isUnauthorized ? NeonLocalizations.of(context).loginAgain : NeonLocalizations.of(context).actionRetry; final onPressed = details.isUnauthorized ? () => _openLoginPage(context) : onRetry; - if (onlyIcon) { - return Semantics( - tooltip: details.getText(context), - child: IconButton( - icon: errorIcon, - padding: EdgeInsets.zero, - visualDensity: const VisualDensity( - horizontal: VisualDensity.minimumDensity, - vertical: VisualDensity.minimumDensity, + switch (type) { + case NeonErrorType.iconOnly: + return Semantics( + tooltip: details.getText(context), + child: IconButton( + icon: errorIcon, + padding: EdgeInsets.zero, + visualDensity: const VisualDensity( + horizontal: VisualDensity.minimumDensity, + vertical: VisualDensity.minimumDensity, + ), + tooltip: actionMessage, + onPressed: onPressed, ), - tooltip: message, - onPressed: onPressed, - ), - ); - } - - return Padding( - padding: const EdgeInsets.all(5), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( + ); + case NeonErrorType.column: + return Padding( + padding: const EdgeInsets.all(5), + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - errorIcon, - const SizedBox( - width: 10, - ), - Flexible( - child: Text( - details.getText(context), - style: TextStyle( - color: color, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + errorIcon, + const SizedBox( + width: 10, ), - ), + Flexible( + child: Text(message, style: textStyle), + ), + ], + ), + ElevatedButton( + onPressed: onPressed, + child: Text(actionMessage), ), ], ), - ElevatedButton( - onPressed: onPressed, - child: Text(message), - ), - ], - ), - ); + ); + case NeonErrorType.listTile: + return ListTile( + leading: errorIcon, + title: Text(message), + titleTextStyle: textStyle, + onTap: onPressed, + ); + } } /// Gets the details for a given [error]. diff --git a/packages/neon/neon/lib/src/widgets/image.dart b/packages/neon/neon/lib/src/widgets/image.dart index da733f1e..a7236b89 100644 --- a/packages/neon/neon/lib/src/widgets/image.dart +++ b/packages/neon/neon/lib/src/widgets/image.dart @@ -186,7 +186,7 @@ class _NeonCachedImageState extends State { onRetry: () { setState(() {}); }, - onlyIcon: true, + type: NeonErrorType.iconOnly, iconSize: widget.size?.shortestSide, ); }