Skip to content

Commit

Permalink
#676 snackbar indicator for failure messages
Browse files Browse the repository at this point in the history
  • Loading branch information
deckerst committed Aug 20, 2023
1 parent 2ba96c7 commit 3f1a645
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 78 deletions.
6 changes: 3 additions & 3 deletions lib/widgets/about/bug_report.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions lib/widgets/collection/entry_set_action_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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));
}
}
},
Expand Down Expand Up @@ -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);
}
}
}
13 changes: 10 additions & 3 deletions lib/widgets/common/action_mixins/entry_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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();
}
},
Expand Down
123 changes: 76 additions & 47 deletions lib/widgets/common/action_mixins/feedback.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -326,56 +331,80 @@ 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(
children: [
Expanded(child: text),
const SizedBox(width: 16),
AnimatedBuilder(
animation: _remainingDurationMillis!,
builder: (context, child) {
final remainingDurationMillis = _remainingDurationMillis!.value;
return CircularIndicator(
radius: 16,
lineWidth: 2,
percent: remainingDurationMillis / _totalDurationMillis!,
background: Colors.grey,
// progress color is provided by the caller,
// because we cannot use the app context theme here
foreground: widget.progressColor,
center: ChangeHighlightText(
'${(remainingDurationMillis / 1000).ceil()}',
style: contentTextStyle.copyWith(
shadows: [
Shadow(
color: timerChangeShadowColor.withOpacity(0),
blurRadius: 0,
)
],
),
changedStyle: contentTextStyle.copyWith(
shadows: [
Shadow(
color: timerChangeShadowColor,
blurRadius: 5,
)
],
),
duration: context.read<DurationsData>().formTextStyleTransition,
),
);
},
),
],
);

return Row(
children: [
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!,
builder: (context, child) {
final remainingDurationMillis = _remainingDurationMillis!.value;
return CircularIndicator(
radius: 16,
lineWidth: 2,
percent: remainingDurationMillis / _totalDurationMillis!,
background: Colors.grey,
// progress color is provided by the caller,
// because we cannot use the app context theme here
foreground: widget.progressColor,
center: ChangeHighlightText(
'${(remainingDurationMillis / 1000).ceil()}',
style: contentTextStyle.copyWith(
shadows: [
Shadow(
color: timerChangeShadowColor.withOpacity(0),
blurRadius: 0,
)
],
),
changedStyle: contentTextStyle.copyWith(
shadows: [
Shadow(
color: timerChangeShadowColor,
blurRadius: 5,
)
],
),
duration: context.read<DurationsData>().formTextStyleTransition,
),
);
},
),
]
],
);
}
}

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;

Expand Down
2 changes: 1 addition & 1 deletion lib/widgets/common/action_mixins/vault_aware.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions lib/widgets/settings/settings_mobile_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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) {
Expand All @@ -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);
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions lib/widgets/viewer/action/entry_action_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions lib/widgets/viewer/action/entry_info_action_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
Loading

0 comments on commit 3f1a645

Please sign in to comment.