feat(ui,train): a pull far enough to refresh ticks, lightly, as the navigation does

AppRefreshIndicator ticks (selectionClick, as a choice on the floating bar
or the rail) once the pull commits: as it snaps back to refresh, or as it
refreshes at once, whichever comes first, and not for a pull let go too
early. Train's list of days pulled to read the band used Flutter's own
RefreshIndicator, the older spinner; it takes the app's now, and the tick
with it.

Whats-New: Pulling a list down to refresh ticks lightly.
Whats-New-De: Eine Liste zum Aktualisieren herunterziehen gibt ein leichtes Tippen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-04 20:23:27 +01:00
commit bcbefe38a3
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1,4 +1,7 @@
import 'dart:async';
import 'package:expressive_loading_indicator/expressive_loading_indicator.dart';
import 'package:flutter/services.dart';
import 'package:material_ui/material_ui.dart';
/// The app's spinner for anything still loading: Material 3 Expressive's
@ -40,7 +43,8 @@ class const ButtonLoadingIndicator({super.key}) extends StatelessWidget {
/// meanwhile, as Material 3 Expressive's pull to refresh has it: in a small
/// round container that comes down with the pull and stays while it
/// refreshes. Flutter's own [RefreshIndicator] draws the older spinner, so
/// it only does the pulling here (its `noSpinner` kind).
/// it only does the pulling here (its `noSpinner` kind). A pull far enough
/// to refresh ticks, lightly, as a choice on the navigation bar does.
class const AppRefreshIndicator({
required final RefreshCallback onRefresh,
required final Widget child,
@ -110,6 +114,15 @@ class _AppRefreshIndicatorState() extends State<AppRefreshIndicator> {
child: RefreshIndicator.noSpinner(
onRefresh: widget.onRefresh,
onStatusChange: (status) => setState(() {
// Snapping back to refresh, or refreshing at once: let go
// far enough, whichever comes first.
const committed = {
RefreshIndicatorStatus.snap,
RefreshIndicatorStatus.refresh,
};
if (committed.contains(status) && !committed.contains(_status)) {
unawaited(HapticFeedback.selectionClick());
}
_status = status;
if (status == RefreshIndicatorStatus.done ||
status == RefreshIndicatorStatus.canceled) {