91 lines
2.6 KiB
Dart
91 lines
2.6 KiB
Dart
|
|
import 'dart:ui' show ImageFilter;
|
||
|
|
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
|
|
||
|
|
import '../../providers/appearance_provider.dart';
|
||
|
|
import '../theme/app_theme.dart';
|
||
|
|
import 'window_chrome.dart';
|
||
|
|
|
||
|
|
|
||
|
|
class AppSliverHeader extends ConsumerWidget {
|
||
|
|
final String? title;
|
||
|
|
final Widget? titleWidget;
|
||
|
|
final Widget? leading;
|
||
|
|
final bool automaticallyImplyLeading;
|
||
|
|
final List<Widget> actions;
|
||
|
|
final PreferredSizeWidget? bottom;
|
||
|
|
final double? expandedHeight;
|
||
|
|
final double? toolbarHeight;
|
||
|
|
|
||
|
|
const AppSliverHeader({
|
||
|
|
super.key,
|
||
|
|
this.title,
|
||
|
|
this.titleWidget,
|
||
|
|
this.leading,
|
||
|
|
this.automaticallyImplyLeading = true,
|
||
|
|
this.actions = const [],
|
||
|
|
this.bottom,
|
||
|
|
this.expandedHeight,
|
||
|
|
this.toolbarHeight,
|
||
|
|
}) : assert(
|
||
|
|
title != null || titleWidget != null,
|
||
|
|
'AppSliverHeader requires either title or titleWidget',
|
||
|
|
);
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
||
|
|
final mode =
|
||
|
|
ref.watch(appearanceProvider).value?.headerMode ?? HeaderMode.frosted;
|
||
|
|
final isFrosted = mode == HeaderMode.frosted;
|
||
|
|
final theme = Theme.of(context);
|
||
|
|
final tokens = context.appTokens;
|
||
|
|
final reserved = WindowChrome.reservedTrailingWidth(tokens);
|
||
|
|
final effectiveActions = <Widget>[
|
||
|
|
...actions,
|
||
|
|
if (reserved > 0) SizedBox(width: reserved),
|
||
|
|
];
|
||
|
|
|
||
|
|
return SliverAppBar(
|
||
|
|
pinned: true,
|
||
|
|
elevation: 0,
|
||
|
|
scrolledUnderElevation: 0,
|
||
|
|
toolbarHeight: toolbarHeight ?? kToolbarHeight,
|
||
|
|
leading: leading,
|
||
|
|
automaticallyImplyLeading: automaticallyImplyLeading,
|
||
|
|
title:
|
||
|
|
titleWidget ??
|
||
|
|
(title != null
|
||
|
|
? Text(
|
||
|
|
title!,
|
||
|
|
style: theme.textTheme.titleMedium,
|
||
|
|
maxLines: 1,
|
||
|
|
overflow: TextOverflow.ellipsis,
|
||
|
|
)
|
||
|
|
: null),
|
||
|
|
actions: effectiveActions,
|
||
|
|
bottom: bottom,
|
||
|
|
expandedHeight: expandedHeight,
|
||
|
|
backgroundColor: isFrosted
|
||
|
|
? theme.colorScheme.surface.withValues(alpha: 0.7)
|
||
|
|
: theme.colorScheme.surface,
|
||
|
|
shape: isFrosted
|
||
|
|
? Border(
|
||
|
|
bottom: BorderSide(
|
||
|
|
color: theme.colorScheme.onSurface.withValues(alpha: 0.08),
|
||
|
|
width: 1,
|
||
|
|
),
|
||
|
|
)
|
||
|
|
: null,
|
||
|
|
flexibleSpace: isFrosted
|
||
|
|
? ClipRect(
|
||
|
|
child: BackdropFilter(
|
||
|
|
filter: ImageFilter.blur(sigmaX: 18, sigmaY: 18),
|
||
|
|
child: const SizedBox.expand(),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
: null,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|