diff --git a/CHANGELOG.md b/CHANGELOG.md index f2515c5..a90a69d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ The package is available from ## 1.1 Consolidate Android Login ++ Export resilient snackbar [1.0.47 20260917 jesscmoore] ++ Overflow menu in More button on mobile nav bar [1.0.46 20260915 jesscmoore] + Add internet connection retries [1.0.48 20260916 jesscmoore] + Overflow menu in More button on mobile nav bar [1.0.47 20260915 jesscmoore] + Plainer wording when a sign-in does not complete [1.0.46 20260918 gjw] diff --git a/lib/solidui.dart b/lib/solidui.dart index a267b3b..514e4bc 100644 --- a/lib/solidui.dart +++ b/lib/solidui.dart @@ -111,6 +111,8 @@ export 'src/services/solid_backup_service.dart'; export 'src/widgets/solid_backup_dialog.dart'; export 'src/utils/snack_bar.dart'; +export 'src/utils/resilient_snack_bar.dart'; +export 'src/constants/ui_colors.dart'; export 'src/widgets/solid_file.dart'; export 'src/widgets/solid_file_browser.dart'; diff --git a/lib/src/utils/resilient_snack_bar.dart b/lib/src/utils/resilient_snack_bar.dart new file mode 100644 index 0000000..895cddf --- /dev/null +++ b/lib/src/utils/resilient_snack_bar.dart @@ -0,0 +1,78 @@ +/// A [SnackBar] reporter that survives a momentarily-unregistered Scaffold. +/// +/// Copyright (C) 2024-2026, Software Innovation Institute, ANU. +/// +/// Licensed under the MIT License (the "License"). +/// +/// License: https://choosealicense.com/licenses/mit/. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +/// +/// Authors: Jess Moore, Anushka Vidanage + +library; + +import 'package:flutter/material.dart'; + +/// Signature for the snack-bar reporter used to surface an async action's +/// outcome back to the caller's UI. + +typedef NotifySnackBar = void Function( + String message, + Color backgroundColor, { + Duration duration, +}); + +/// Build a resilient [NotifySnackBar] bound to [messenger]. +/// +/// Showing a SnackBar requires a Scaffold registered with the messenger. +/// Depending on how the host embeds the form, and on the exact moment the +/// dialog is dismissed, the messenger can momentarily have no Scaffold, +/// which throws the "_scaffolds.isNotEmpty" assertion. A confirmation toast +/// is non-critical (the underlying action has already succeeded or failed +/// independently of this call), so the call is guarded and retried once on +/// the next frame rather than ever letting it crash the app. +/// +/// Capture the closure returned here *before* any `await` in the caller, so +/// it keeps working even if the calling widget is unmounted mid-flow (e.g. a +/// purchase or permission-grant result that resolves after the dialog that +/// started it has already closed). + +NotifySnackBar makeResilientSnackBar(ScaffoldMessengerState messenger) => ( + String message, + Color backgroundColor, { + Duration duration = const Duration(seconds: 4), + }) { + SnackBar build() => SnackBar( + content: Text(message), + backgroundColor: backgroundColor, + duration: duration, + ); + try { + messenger.showSnackBar(build()); + } on Object catch (_) { + WidgetsBinding.instance.addPostFrameCallback((_) { + try { + messenger.showSnackBar(build()); + } on Object catch (e) { + debugPrint('Could not show snackbar "$message": $e'); + } + }); + } + }; diff --git a/lib/src/widgets/grant_permission_notify.dart b/lib/src/widgets/grant_permission_notify.dart index d2adcd7..fa86fc6 100644 --- a/lib/src/widgets/grant_permission_notify.dart +++ b/lib/src/widgets/grant_permission_notify.dart @@ -36,48 +36,12 @@ import 'package:solidpod/solidpod.dart' show RecipientNotReadyException, RecipientType, sendNotification; import 'package:solidui/src/constants/ui_colors.dart' show ActionColors; +import 'package:solidui/src/utils/resilient_snack_bar.dart'; -/// Signature for the snack-bar reporter used to surface notification -/// delivery outcomes back to the caller's UI. - -typedef NotifySnackBar = void Function( - String message, - Color backgroundColor, { - Duration duration, -}); - -/// Build a resilient [NotifySnackBar] bound to [messenger]. -/// -/// Showing a SnackBar requires a Scaffold registered with the messenger. -/// Depending on how the host embeds the form, and on the exact moment the -/// dialog is dismissed, the messenger can momentarily have no Scaffold, -/// which throws the "_scaffolds.isNotEmpty" assertion. A confirmation toast -/// is non-critical (the grant has already succeeded), so the call is guarded -/// and retried once on the next frame rather than ever letting it crash the -/// app. - -NotifySnackBar makeResilientSnackBar(ScaffoldMessengerState messenger) => ( - String message, - Color backgroundColor, { - Duration duration = const Duration(seconds: 4), - }) { - SnackBar build() => SnackBar( - content: Text(message), - backgroundColor: backgroundColor, - duration: duration, - ); - try { - messenger.showSnackBar(build()); - } on Object catch (_) { - WidgetsBinding.instance.addPostFrameCallback((_) { - try { - messenger.showSnackBar(build()); - } on Object catch (e) { - debugPrint('Could not show snackbar "$message": $e'); - } - }); - } - }; +// Re-exported so existing importers of this file (e.g. +// grant_permission_form.dart) keep resolving `makeResilientSnackBar`/ +// `NotifySnackBar` without changes now that they live in their own file. +export 'package:solidui/src/utils/resilient_snack_bar.dart'; /// Send a "resource shared" notification to each recipient WebID and /// report delivery problems through [showSnack]. diff --git a/pubspec.yaml b/pubspec.yaml index 9a69b2d..c30b0a3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: solidui description: 'A UI library for building Solid applications with Flutter.' -version: 1.0.46 +version: 1.0.47 homepage: https://github.com/anusii/solidui # Scaffold a new Solid Pod file-browser app (a pod browser, with navigation