feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
|
||||
import '../l10n/l10n.dart';
|
||||
import '../theme/theme.dart';
|
||||
import 'directional_transition_scope.dart';
|
||||
|
||||
/// Minimum height of the frosted app bar content area below the safe area.
|
||||
const _kBarContentMinHeight = Grid.xxs + 32 + Grid.xxs; // 48
|
||||
const _kBottomBorderWidth = 1.0;
|
||||
|
||||
TextStyle _effectiveTitleStyle(BuildContext context, TextStyle? titleStyle) {
|
||||
final baseStyle =
|
||||
context.textTheme.titleMedium ??
|
||||
const TextStyle(fontSize: 20, height: 1.3);
|
||||
return baseStyle.copyWith(fontWeight: FontWeight.w600).merge(titleStyle);
|
||||
}
|
||||
|
||||
double _barContentHeight(
|
||||
BuildContext context,
|
||||
TextStyle? titleStyle,
|
||||
double titleContentHeight,
|
||||
) {
|
||||
final style = _effectiveTitleStyle(context, titleStyle);
|
||||
final scaledFontSize = MediaQuery.textScalerOf(
|
||||
context,
|
||||
).scale(style.fontSize ?? 20);
|
||||
final scaledTitleHeight = scaledFontSize * (style.height ?? 1);
|
||||
final effectiveTitleHeight = titleContentHeight > scaledTitleHeight
|
||||
? titleContentHeight
|
||||
: scaledTitleHeight;
|
||||
final accessibleHeight = Grid.xxs + effectiveTitleHeight + Grid.xxs;
|
||||
return accessibleHeight > _kBarContentMinHeight
|
||||
? accessibleHeight
|
||||
: _kBarContentMinHeight;
|
||||
}
|
||||
|
||||
/// Height for a compact title rail below the app bar's action row.
|
||||
///
|
||||
/// The rail normally stays at 40dp, but grows with an accessible title rather
|
||||
/// than clipping text at larger system text sizes.
|
||||
double frostedAppBarLowerTitleHeight(
|
||||
BuildContext context, {
|
||||
TextStyle? titleStyle,
|
||||
}) {
|
||||
final style = _effectiveTitleStyle(context, titleStyle);
|
||||
final scaledFontSize = MediaQuery.textScalerOf(
|
||||
context,
|
||||
).scale(style.fontSize ?? 20);
|
||||
final titleHeight = scaledFontSize * (style.height ?? 1);
|
||||
return titleHeight > 40 ? titleHeight : 40;
|
||||
}
|
||||
|
||||
/// Returns the total height of the [FrostedAppBar] including safe area padding.
|
||||
///
|
||||
/// Use this to add top spacing to body content so it starts below the bar.
|
||||
/// Pass the same [titleStyle] and [titleContentHeight] to the bar and this
|
||||
/// helper when customizing them.
|
||||
double frostedAppBarHeight(
|
||||
BuildContext context, {
|
||||
double bottomHeight = 0,
|
||||
TextStyle? titleStyle,
|
||||
double titleContentHeight = 0,
|
||||
}) {
|
||||
return MediaQuery.paddingOf(context).top +
|
||||
_barContentHeight(context, titleStyle, titleContentHeight) +
|
||||
bottomHeight +
|
||||
_kBottomBorderWidth;
|
||||
}
|
||||
|
||||
/// A frosted-glass floating app bar designed to sit inside a [Stack].
|
||||
///
|
||||
/// Renders as a [Positioned] widget pinned to the top of its parent Stack.
|
||||
/// Content scrolls underneath with a translucent backdrop blur effect.
|
||||
class FrostedAppBar extends StatelessWidget {
|
||||
/// Widget displayed on the leading (left) side. If null and the navigator
|
||||
/// can pop, a back button is shown automatically.
|
||||
final Widget? leading;
|
||||
|
||||
/// Whether to infer a back button from the current navigator.
|
||||
final bool automaticallyImplyLeading;
|
||||
|
||||
/// Widget displayed in the center/title area.
|
||||
final Widget? title;
|
||||
|
||||
/// Optional style merged over the default title style.
|
||||
final TextStyle? titleStyle;
|
||||
|
||||
/// Scaled height needed by a custom title with multiple text lines.
|
||||
///
|
||||
/// Pass the same value to [frostedAppBarHeight] when spacing body content.
|
||||
final double titleContentHeight;
|
||||
|
||||
/// Optional content displayed below the title row in the same surface.
|
||||
final Widget? bottom;
|
||||
|
||||
/// Height reserved for [bottom].
|
||||
final double bottomHeight;
|
||||
|
||||
/// Extends [bottom] upward into the title row without moving the app bar's
|
||||
/// outer bounds. This keeps overlapping controls inside the app bar's hit
|
||||
/// test region as well as its paint region.
|
||||
final double bottomOverlap;
|
||||
|
||||
/// Widgets displayed on the trailing (right) side.
|
||||
final List<Widget> actions;
|
||||
|
||||
/// Horizontal inset for the app bar's leading, title, and actions.
|
||||
final double horizontalInset;
|
||||
|
||||
/// Color applied to icons in the app bar.
|
||||
final Color? iconColor;
|
||||
|
||||
/// Paints over the frosted fill instead of the default translucent surface.
|
||||
/// Used by the Buzz themes to carry their branded gradient across the app's
|
||||
/// top section — see [buzzTopSectionGradient].
|
||||
final Gradient? gradient;
|
||||
|
||||
/// Whether to apply the translucent blur treatment behind the app bar.
|
||||
///
|
||||
/// A page can leave its painted backdrop exposed at rest, then turn this on
|
||||
/// when scrolling moves content beneath the controls.
|
||||
final bool frosted;
|
||||
|
||||
/// Opacity of the frosted surface above the blurred backdrop.
|
||||
final double frostedSurfaceOpacity;
|
||||
|
||||
/// Blur strength of the frosted backdrop.
|
||||
final double frostedBlurSigma;
|
||||
|
||||
/// Whether to draw a divider below the app bar.
|
||||
final bool showBottomDivider;
|
||||
|
||||
/// Opacity of the divider below the app bar.
|
||||
final double bottomDividerOpacity;
|
||||
|
||||
const FrostedAppBar({
|
||||
super.key,
|
||||
this.leading,
|
||||
this.automaticallyImplyLeading = true,
|
||||
this.title,
|
||||
this.titleStyle,
|
||||
this.titleContentHeight = 0,
|
||||
this.bottom,
|
||||
this.bottomHeight = 0,
|
||||
this.bottomOverlap = 0,
|
||||
this.actions = const [],
|
||||
this.horizontalInset = Grid.quarter,
|
||||
this.iconColor,
|
||||
this.gradient,
|
||||
this.frosted = true,
|
||||
this.frostedSurfaceOpacity = 0.5,
|
||||
this.frostedBlurSigma = 20,
|
||||
this.showBottomDivider = true,
|
||||
this.bottomDividerOpacity = 0.15,
|
||||
}) : assert(bottom == null || bottomHeight > 0),
|
||||
assert(bottomOverlap >= 0),
|
||||
assert(bottom != null || bottomOverlap == 0),
|
||||
assert(frostedBlurSigma >= 0),
|
||||
assert(bottomDividerOpacity >= 0 && bottomDividerOpacity <= 1);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final topPadding = MediaQuery.paddingOf(context).top;
|
||||
final canPop = Navigator.canPop(context);
|
||||
final effectiveTitleStyle = _effectiveTitleStyle(context, titleStyle);
|
||||
final barContentHeight = _barContentHeight(
|
||||
context,
|
||||
titleStyle,
|
||||
titleContentHeight,
|
||||
);
|
||||
|
||||
final effectiveLeading =
|
||||
leading ??
|
||||
(automaticallyImplyLeading && canPop
|
||||
? SizedBox(
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
color: iconColor,
|
||||
icon: const Icon(LucideIcons.chevronLeft),
|
||||
tooltip: context.l10n.back,
|
||||
),
|
||||
)
|
||||
: null);
|
||||
|
||||
final titleRow = SizedBox(
|
||||
height: barContentHeight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: horizontalInset),
|
||||
child: IconTheme.merge(
|
||||
data: IconThemeData(color: iconColor),
|
||||
child: Row(
|
||||
children: [
|
||||
?effectiveLeading,
|
||||
if (title != null)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: effectiveLeading != null
|
||||
? 0
|
||||
: horizontalInset < Grid.gutter
|
||||
? Grid.gutter - horizontalInset
|
||||
: 0,
|
||||
right: actions.isEmpty
|
||||
? horizontalInset < Grid.gutter
|
||||
? Grid.gutter - horizontalInset
|
||||
: 0
|
||||
: 0,
|
||||
),
|
||||
child: DefaultTextStyle.merge(
|
||||
style: effectiveTitleStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
child: title!,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
const Spacer(),
|
||||
...actions,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final contentBody = bottom != null && bottomOverlap > 0
|
||||
? SizedBox(
|
||||
height: barContentHeight + bottomHeight,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(top: 0, left: 0, right: 0, child: titleRow),
|
||||
Positioned(
|
||||
top: barContentHeight - bottomOverlap,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: bottomHeight + bottomOverlap,
|
||||
child: bottom!,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
titleRow,
|
||||
if (bottom != null) SizedBox(height: bottomHeight, child: bottom),
|
||||
],
|
||||
);
|
||||
|
||||
final content = DirectionalTransitionMotion(
|
||||
transformKey: const ValueKey(
|
||||
'frosted-app-bar-content-transition-transform',
|
||||
),
|
||||
opacityKey: const ValueKey('frosted-app-bar-content-transition-opacity'),
|
||||
child: contentBody,
|
||||
);
|
||||
|
||||
final background = Container(
|
||||
key: const ValueKey('frosted-app-bar-background'),
|
||||
padding: EdgeInsets.only(top: topPadding),
|
||||
decoration: BoxDecoration(
|
||||
color: !frosted
|
||||
? Colors.transparent
|
||||
: gradient == null
|
||||
? context.colors.surface.withValues(alpha: frostedSurfaceOpacity)
|
||||
: null,
|
||||
gradient: gradient,
|
||||
border: showBottomDivider
|
||||
? Border(
|
||||
bottom: BorderSide(
|
||||
color: navigationDivider(context, bottomDividerOpacity),
|
||||
width: _kBottomBorderWidth,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: content,
|
||||
);
|
||||
|
||||
final child = ClipRect(
|
||||
child: frosted
|
||||
? BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: frostedBlurSigma,
|
||||
sigmaY: frostedBlurSigma,
|
||||
),
|
||||
child: background,
|
||||
)
|
||||
: background,
|
||||
);
|
||||
|
||||
return Positioned(top: 0, left: 0, right: 0, child: child);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user