#676 snackbar indicator for failure messages
This commit is contained in:
parent
2ba96c78b2
commit
3f1a6452e5
13 changed files with 119 additions and 78 deletions
|
@ -174,16 +174,16 @@ class _BugReportState extends State<BugReport> with FeedbackMixin {
|
|||
);
|
||||
if (success != null) {
|
||||
if (success) {
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
} else {
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _copySystemInfo() async {
|
||||
await Clipboard.setData(ClipboardData(text: await _infoLoader));
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
|
||||
Future<void> _goToGithub() => AvesApp.launchUrl(bugReportUrl);
|
||||
|
|
|
@ -321,7 +321,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final successCount = successOps.length;
|
||||
if (successCount < todoCount) {
|
||||
final count = todoCount - successCount;
|
||||
showFeedback(context, context.l10n.collectionDeleteFailureFeedback(count));
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.collectionDeleteFailureFeedback(count));
|
||||
}
|
||||
|
||||
// cleanup
|
||||
|
@ -438,10 +438,10 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
final successCount = successOps.length;
|
||||
if (successCount < todoCount) {
|
||||
final count = todoCount - successCount;
|
||||
showFeedback(context, l10n.collectionEditFailureFeedback(count));
|
||||
showFeedback(context, FeedbackType.warn, l10n.collectionEditFailureFeedback(count));
|
||||
} else {
|
||||
final count = editedOps.length;
|
||||
showFeedback(context, l10n.collectionEditSuccessFeedback(count));
|
||||
showFeedback(context, FeedbackType.info, l10n.collectionEditSuccessFeedback(count));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -723,7 +723,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
|||
|
||||
await appService.pinToHomeScreen(name, coverEntry, filters: filters);
|
||||
if (!device.showPinShortcutFeedback) {
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,12 +116,14 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
final count = selectionCount - successCount;
|
||||
showFeedback(
|
||||
context,
|
||||
FeedbackType.warn,
|
||||
l10n.collectionExportFailureFeedback(count),
|
||||
showAction,
|
||||
);
|
||||
} else {
|
||||
showFeedback(
|
||||
context,
|
||||
FeedbackType.info,
|
||||
l10n.genericSuccessFeedback,
|
||||
showAction,
|
||||
);
|
||||
|
@ -226,7 +228,11 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
final successCount = successOps.length;
|
||||
if (successCount < todoCount) {
|
||||
final count = todoCount - successCount;
|
||||
showFeedback(context, copy ? l10n.collectionCopyFailureFeedback(count) : l10n.collectionMoveFailureFeedback(count));
|
||||
showFeedback(
|
||||
context,
|
||||
FeedbackType.warn,
|
||||
copy ? l10n.collectionCopyFailureFeedback(count) : l10n.collectionMoveFailureFeedback(count),
|
||||
);
|
||||
} else {
|
||||
final count = movedOps.length;
|
||||
final appMode = context.read<ValueNotifier<AppMode>?>()?.value;
|
||||
|
@ -268,6 +274,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
if (!toBin || (toBin && settings.confirmAfterMoveToBin)) {
|
||||
showFeedback(
|
||||
context,
|
||||
FeedbackType.info,
|
||||
copy ? l10n.collectionCopySuccessFeedback(count) : l10n.collectionMoveSuccessFeedback(count),
|
||||
action,
|
||||
);
|
||||
|
@ -366,10 +373,10 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
|
|||
final successCount = successOps.length;
|
||||
if (successCount < todoCount) {
|
||||
final count = todoCount - successCount;
|
||||
showFeedback(context, l10n.collectionRenameFailureFeedback(count));
|
||||
showFeedback(context, FeedbackType.warn, l10n.collectionRenameFailureFeedback(count));
|
||||
} else {
|
||||
final count = movedOps.length;
|
||||
showFeedback(context, l10n.collectionRenameSuccessFeedback(count));
|
||||
showFeedback(context, FeedbackType.info, l10n.collectionRenameSuccessFeedback(count));
|
||||
onSuccess?.call();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -18,10 +18,12 @@ import 'package:overlay_support/overlay_support.dart';
|
|||
import 'package:percent_indicator/circular_percent_indicator.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
enum FeedbackType { info, warn }
|
||||
|
||||
mixin FeedbackMixin {
|
||||
void dismissFeedback(BuildContext context) => ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
|
||||
void showFeedback(BuildContext context, String message, [SnackBarAction? action]) {
|
||||
void showFeedback(BuildContext context, FeedbackType type, String message, [SnackBarAction? action]) {
|
||||
ScaffoldMessengerState? scaffoldMessenger;
|
||||
try {
|
||||
scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
|
@ -31,18 +33,19 @@ mixin FeedbackMixin {
|
|||
debugPrint('failed to find ScaffoldMessenger in context');
|
||||
}
|
||||
if (scaffoldMessenger != null) {
|
||||
showFeedbackWithMessenger(context, scaffoldMessenger, message, action);
|
||||
showFeedbackWithMessenger(context, scaffoldMessenger, type, message, action);
|
||||
}
|
||||
}
|
||||
|
||||
// provide the messenger if feedback happens as the widget is disposed
|
||||
void showFeedbackWithMessenger(BuildContext context, ScaffoldMessengerState messenger, String message, [SnackBarAction? action]) {
|
||||
void showFeedbackWithMessenger(BuildContext context, ScaffoldMessengerState messenger, FeedbackType type, String message, [SnackBarAction? action]) {
|
||||
settings.timeToTakeAction.getSnackBarDuration(action != null).then((duration) {
|
||||
final start = DateTime.now();
|
||||
final theme = Theme.of(context);
|
||||
final snackBarTheme = theme.snackBarTheme;
|
||||
|
||||
final snackBarContent = _FeedbackMessage(
|
||||
type: type,
|
||||
message: message,
|
||||
progressColor: theme.colorScheme.secondary,
|
||||
start: start,
|
||||
|
@ -274,11 +277,13 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
|
|||
}
|
||||
|
||||
class _FeedbackMessage extends StatefulWidget {
|
||||
final FeedbackType type;
|
||||
final String message;
|
||||
final DateTime? start, stop;
|
||||
final Color progressColor;
|
||||
|
||||
const _FeedbackMessage({
|
||||
required this.type,
|
||||
required this.message,
|
||||
required this.progressColor,
|
||||
this.start,
|
||||
|
@ -326,15 +331,23 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final text = Text(widget.message);
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final theme = Theme.of(context);
|
||||
final contentTextStyle = theme.snackBarTheme.contentTextStyle ?? ThemeData(brightness: theme.brightness).textTheme.titleMedium!;
|
||||
final fontSize = theme.snackBarTheme.contentTextStyle?.fontSize ?? theme.textTheme.bodyMedium!.fontSize!;
|
||||
final timerChangeShadowColor = theme.colorScheme.primary;
|
||||
return _remainingDurationMillis == null
|
||||
? text
|
||||
: Row(
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(child: text),
|
||||
if (widget.type == FeedbackType.warn) ...[
|
||||
CustomPaint(
|
||||
painter: _WarnIndicator(),
|
||||
size: Size(4, fontSize * textScaleFactor),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
Expanded(child: Text(widget.message)),
|
||||
if (_remainingDurationMillis != null) ...[
|
||||
const SizedBox(width: 16),
|
||||
AnimatedBuilder(
|
||||
animation: _remainingDurationMillis!,
|
||||
|
@ -371,11 +384,27 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
|
|||
);
|
||||
},
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WarnIndicator extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
canvas.drawRRect(
|
||||
RRect.fromLTRBR(0, 0, size.width, size.height, Radius.circular(size.shortestSide / 2)),
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..color = Colors.amber,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_WarnIndicator oldDelegate) => false;
|
||||
}
|
||||
|
||||
class ActionFeedback extends StatefulWidget {
|
||||
final Widget? child;
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ mixin VaultAwareMixin on FeedbackMixin {
|
|||
Future<bool> unlockAlbum(BuildContext context, String dirPath) async {
|
||||
final success = await _tryUnlock(dirPath, context);
|
||||
if (!success) {
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import 'package:aves/theme/durations.dart';
|
|||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/view/view.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/entry_storage.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/tile_extent_controller.dart';
|
||||
import 'package:aves/widgets/dialogs/aves_confirmation_dialog.dart';
|
||||
|
@ -255,7 +256,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
}
|
||||
},
|
||||
);
|
||||
showFeedback(context, l10n.genericSuccessFeedback, showAction);
|
||||
showFeedback(context, FeedbackType.info, l10n.genericSuccessFeedback, showAction);
|
||||
}
|
||||
|
||||
Future<void> _delete(BuildContext context, Set<AlbumFilter> filters) async {
|
||||
|
@ -363,7 +364,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
final successCount = successOps.length;
|
||||
if (successCount < todoCount) {
|
||||
final count = todoCount - successCount;
|
||||
showFeedbackWithMessenger(context, messenger, l10n.collectionDeleteFailureFeedback(count));
|
||||
showFeedbackWithMessenger(context, messenger, FeedbackType.warn, l10n.collectionDeleteFailureFeedback(count));
|
||||
}
|
||||
|
||||
// cleanup
|
||||
|
@ -442,9 +443,9 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
|
|||
final successCount = successOps.length;
|
||||
if (successCount < todoCount) {
|
||||
final count = todoCount - successCount;
|
||||
showFeedbackWithMessenger(context, messenger, l10n.collectionMoveFailureFeedback(count));
|
||||
showFeedbackWithMessenger(context, messenger, FeedbackType.warn, l10n.collectionMoveFailureFeedback(count));
|
||||
} else {
|
||||
showFeedbackWithMessenger(context, messenger, l10n.genericSuccessFeedback);
|
||||
showFeedbackWithMessenger(context, messenger, FeedbackType.info, l10n.genericSuccessFeedback);
|
||||
}
|
||||
|
||||
// cleanup
|
||||
|
|
|
@ -119,9 +119,9 @@ class _SettingsMobilePageState extends State<SettingsMobilePage> with FeedbackMi
|
|||
);
|
||||
if (success != null) {
|
||||
if (success) {
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
} else {
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
}
|
||||
case SettingsAction.import:
|
||||
|
@ -141,7 +141,7 @@ class _SettingsMobilePageState extends State<SettingsMobilePage> with FeedbackMi
|
|||
} else {
|
||||
if (allJsonMap is! Map) {
|
||||
debugPrint('failed to import app json=$allJsonMap');
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||
return;
|
||||
}
|
||||
allJsonMap.keys.where((v) => v != exportVersionKey).forEach((k) {
|
||||
|
@ -165,10 +165,10 @@ class _SettingsMobilePageState extends State<SettingsMobilePage> with FeedbackMi
|
|||
await Future.forEach<AppExportItem>(toImport, (item) async {
|
||||
return item.import(importable[item], source);
|
||||
});
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
} catch (error) {
|
||||
debugPrint('failed to import app json, error=$error');
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,7 +184,11 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
_addShortcut(context, targetEntry);
|
||||
case EntryAction.copyToClipboard:
|
||||
appService.copyToClipboard(targetEntry.uri, targetEntry.bestTitle).then((success) {
|
||||
showFeedback(context, success ? context.l10n.genericSuccessFeedback : context.l10n.genericFailureFeedback);
|
||||
if (success) {
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
} else {
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
});
|
||||
case EntryAction.delete:
|
||||
_delete(context, targetEntry);
|
||||
|
@ -338,7 +342,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
|
||||
await appService.pinToHomeScreen(name, targetEntry, uri: targetEntry.uri);
|
||||
if (!device.showPinShortcutFeedback) {
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,7 +379,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
if (!await checkStoragePermission(context, {targetEntry})) return;
|
||||
|
||||
if (!await targetEntry.delete()) {
|
||||
showFeedback(context, l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback);
|
||||
} else {
|
||||
final source = context.read<CollectionSource>();
|
||||
if (source.initState != SourceInitializationState.none) {
|
||||
|
|
|
@ -217,9 +217,9 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
|
|||
);
|
||||
if (success != null) {
|
||||
if (success) {
|
||||
showFeedback(context, context.l10n.genericSuccessFeedback);
|
||||
showFeedback(context, FeedbackType.info, context.l10n.genericSuccessFeedback);
|
||||
} else {
|
||||
showFeedback(context, context.l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.genericFailureFeedback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,9 @@ mixin SingleEntryEditorMixin on FeedbackMixin, PermissionAwareMixin {
|
|||
await targetEntry.catalog(background: background, force: dataTypes.contains(EntryDataType.catalog), persist: persist);
|
||||
await targetEntry.locate(background: background, force: dataTypes.contains(EntryDataType.address), geocoderLocale: settings.appliedLocale);
|
||||
}
|
||||
showFeedback(context, l10n.genericSuccessFeedback);
|
||||
showFeedback(context, FeedbackType.info, l10n.genericSuccessFeedback);
|
||||
} else {
|
||||
showFeedback(context, l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback);
|
||||
}
|
||||
} catch (error, stack) {
|
||||
await reportService.recordError(error, stack);
|
||||
|
|
|
@ -131,9 +131,9 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
|||
},
|
||||
)
|
||||
: null;
|
||||
showFeedback(context, l10n.genericSuccessFeedback, showAction);
|
||||
showFeedback(context, FeedbackType.info, l10n.genericSuccessFeedback, showAction);
|
||||
} else {
|
||||
showFeedback(context, l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin {
|
|||
fields = await embeddedDataService.extractXmpDataProp(entry, notification.props, notification.mimeType);
|
||||
}
|
||||
if (!fields.containsKey('mimeType') || !fields.containsKey('uri')) {
|
||||
showFeedback(context, context.l10n.viewerInfoOpenEmbeddedFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, context.l10n.viewerInfoOpenEmbeddedFailureFeedback);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin {
|
|||
if (success) {
|
||||
await SystemNavigator.pop();
|
||||
} else {
|
||||
showFeedback(context, l10n.genericFailureFeedback);
|
||||
showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue