mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-22 07:18:38 +02:00
193 lines
5.9 KiB
Dart
193 lines
5.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:spotiflac_android/l10n/l10n.dart';
|
|
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
|
import 'package:spotiflac_android/widgets/mornye_chrome.dart';
|
|
|
|
/// Applies the same transient-message policy to existing snackbar call sites.
|
|
/// A new message replaces the old one instead of building a stale FIFO queue.
|
|
class AppScaffoldMessenger extends ScaffoldMessenger {
|
|
const AppScaffoldMessenger({super.key, required super.child});
|
|
|
|
@override
|
|
ScaffoldMessengerState createState() => _AppScaffoldMessengerState();
|
|
}
|
|
|
|
class _AppScaffoldMessengerState extends ScaffoldMessengerState {
|
|
@override
|
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(
|
|
SnackBar snackBar, {
|
|
AnimationStyle? snackBarAnimationStyle,
|
|
}) {
|
|
removeCurrentSnackBar();
|
|
return super.showSnackBar(
|
|
_TransientSnackBar(snackBar),
|
|
snackBarAnimationStyle: snackBarAnimationStyle,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _TransientSnackBar extends SnackBar {
|
|
_TransientSnackBar(this.original)
|
|
: super(
|
|
content: original.content,
|
|
duration: original.duration > const Duration(seconds: 3)
|
|
? const Duration(seconds: 3)
|
|
: original.duration,
|
|
persist: false,
|
|
);
|
|
|
|
final SnackBar original;
|
|
|
|
@override
|
|
SnackBar withAnimation(Animation<double> newAnimation, {Key? fallbackKey}) {
|
|
// Delegate first so custom glass animations remain intact.
|
|
final animated = original.withAnimation(
|
|
newAnimation,
|
|
fallbackKey: fallbackKey,
|
|
);
|
|
return SnackBar(
|
|
key: animated.key,
|
|
content: animated.content,
|
|
backgroundColor: animated.backgroundColor,
|
|
elevation: animated.elevation,
|
|
margin: animated.margin,
|
|
padding: animated.padding,
|
|
width: animated.width,
|
|
shape: animated.shape,
|
|
hitTestBehavior: animated.hitTestBehavior,
|
|
behavior: animated.behavior,
|
|
action: animated.action,
|
|
actionOverflowThreshold: animated.actionOverflowThreshold,
|
|
showCloseIcon: animated.showCloseIcon ?? true,
|
|
closeIconColor: animated.closeIconColor,
|
|
duration: duration,
|
|
persist: false,
|
|
animation: animated.animation,
|
|
onVisible: animated.onVisible,
|
|
dismissDirection: animated.dismissDirection,
|
|
clipBehavior: animated.clipBehavior,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Reports a failure to open a local file with a plain Material snackbar.
|
|
void showCannotOpenFileSnackBar(BuildContext context, Object error) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(
|
|
context.l10n.snackbarCannotOpenFile(context.friendlyError(error)),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Transient messages use the same glass as confirmation dialogs in Mornye.
|
|
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showAppSnackBar(
|
|
BuildContext context, {
|
|
required Widget content,
|
|
Duration duration = const Duration(seconds: 3),
|
|
SnackBarBehavior? behavior,
|
|
}) {
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
if (!context.isMornye) {
|
|
return messenger.showSnackBar(
|
|
SnackBar(
|
|
content: content,
|
|
duration: duration,
|
|
behavior: behavior,
|
|
persist: false,
|
|
showCloseIcon: true,
|
|
),
|
|
);
|
|
}
|
|
final theme = Theme.of(context);
|
|
final reduceMotion = MediaQuery.disableAnimationsOf(context);
|
|
final snackBar = _MornyeSnackBar(
|
|
duration: duration,
|
|
reduceMotion: reduceMotion,
|
|
content: Theme(
|
|
data: theme,
|
|
child: MornyeGlassPanel.overlay(
|
|
radius: 24,
|
|
// Messages have no modal scrim; let more of the page show through.
|
|
tintOpacity: 0.48,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
|
child: DefaultTextStyle(
|
|
style: theme.textTheme.bodyMedium!.copyWith(
|
|
color: theme.colorScheme.onSurface,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(child: content),
|
|
IconButton(
|
|
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
|
|
color: theme.colorScheme.onSurface,
|
|
onPressed: messenger.hideCurrentSnackBar,
|
|
icon: const Icon(Icons.close, size: 20),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
return messenger.showSnackBar(snackBar);
|
|
}
|
|
|
|
class _MornyeSnackBar extends SnackBar {
|
|
const _MornyeSnackBar({
|
|
required super.content,
|
|
required super.duration,
|
|
required this.reduceMotion,
|
|
}) : super(
|
|
behavior: SnackBarBehavior.floating,
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
clipBehavior: Clip.none,
|
|
persist: false,
|
|
showCloseIcon: false,
|
|
padding: EdgeInsets.zero,
|
|
margin: const EdgeInsets.fromLTRB(20, 8, 20, 12),
|
|
shape: const RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.all(Radius.circular(24)),
|
|
),
|
|
);
|
|
|
|
final bool reduceMotion;
|
|
|
|
@override
|
|
SnackBar withAnimation(Animation<double> newAnimation, {Key? fallbackKey}) {
|
|
// Keep the messenger's controller/timer for queuing and dismissal, but
|
|
// replace Material's opacity layer with movement so blur samples the page.
|
|
return SnackBar(
|
|
key: key ?? fallbackKey,
|
|
duration: duration,
|
|
persist: false,
|
|
showCloseIcon: false,
|
|
behavior: behavior,
|
|
backgroundColor: backgroundColor,
|
|
elevation: elevation,
|
|
clipBehavior: clipBehavior,
|
|
padding: padding,
|
|
margin: margin,
|
|
shape: shape,
|
|
animation: const AlwaysStoppedAnimation(1),
|
|
content: AnimatedBuilder(
|
|
animation: newAnimation,
|
|
child: content,
|
|
builder: (context, child) => Transform.translate(
|
|
offset: Offset(
|
|
0,
|
|
reduceMotion
|
|
? 0
|
|
: 8 * (1 - Curves.easeOutCubic.transform(newAnimation.value)),
|
|
),
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|