Files

136 lines
4.5 KiB
Dart
Raw Permalink Normal View History

2026-07-14 11:11:36 +08:00
import 'dart:io' show Platform;
import 'dart:math' as math;
import 'dart:ui' show ImageFilter;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../providers/appearance_provider.dart';
import '../../../shared/theme/app_theme.dart';
import '../../../shared/widgets/top_drag_area.dart';
import '../../../shared/widgets/window_chrome.dart';
import '../../../shared/widgets/window_controls.dart';
const double _kDesktopTitleBarHeight = 38.0;
class DetailNavBar extends ConsumerWidget {
final ValueListenable<double> scrollProgress;
final String title;
final VoidCallback onBack;
final Color chromeColor;
const DetailNavBar({
super.key,
required this.scrollProgress,
required this.title,
required this.onBack,
this.chromeColor = const Color(0xFF08090c),
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isDesktop = WindowChrome.isDesktop;
final topPadding = isDesktop
? _kDesktopTitleBarHeight
: math.max(MediaQuery.paddingOf(context).top, _kDesktopTitleBarHeight);
final tokens = context.appTokens;
final headerMode =
ref.watch(appearanceProvider).value?.headerMode ?? HeaderMode.frosted;
final isFrosted = headerMode == HeaderMode.frosted;
final navHeight = topPadding + 30;
return Stack(
children: [
SizedBox(
height: navHeight,
width: double.infinity,
child: ValueListenableBuilder<double>(
valueListenable: scrollProgress,
builder: (_, raw, _) {
final p = raw.clamp(0.0, 1.0);
if (isFrosted) {
if (Platform.isAndroid && p < 0.05) {
return const SizedBox.expand();
}
final maxSigma = Platform.isAndroid ? 10.0 : 18.0;
return ClipRect(
child: BackdropFilter(
filter: ImageFilter.blur(
sigmaX: p * maxSigma,
sigmaY: p * maxSigma,
),
child: DecoratedBox(
decoration: BoxDecoration(
color: chromeColor.withValues(alpha: p * 0.7),
border: Border(
bottom: BorderSide(
color: const Color(
0xFFFFFFFF,
).withValues(alpha: p * 0.08),
width: 1,
),
),
),
),
),
);
}
return ColoredBox(color: chromeColor.withValues(alpha: p));
},
),
),
const Positioned.fill(child: TopDragArea(child: SizedBox.expand())),
Positioned(
top: isDesktop
? tokens.chromeInset
: MediaQuery.paddingOf(context).top,
left: 0,
right: 0,
child: Row(
children: [
SizedBox(width: tokens.chromeInset),
IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
size: 18,
),
onPressed: onBack,
tooltip: '返回',
constraints: const BoxConstraints(minWidth: 30, minHeight: 30),
padding: const EdgeInsets.all(6),
),
const SizedBox(width: 4),
Expanded(
child: IgnorePointer(
child: ValueListenableBuilder<double>(
valueListenable: scrollProgress,
builder: (_, p, child) {
return Opacity(opacity: p.clamp(0.0, 1.0), child: child);
},
child: Text(
title,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
),
),
),
const SizedBox(width: 4),
if (isDesktop) const WindowControls.forDarkSurface(),
SizedBox(width: tokens.chromeInset),
],
),
),
],
);
}
}