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

Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
2026-08-13 18:34:25 +08:00
parent 61c3fa1df9
commit 9dfa06ffee
3785 changed files with 1085458 additions and 2 deletions
@@ -0,0 +1,177 @@
part of '../pairing_qr_scanner.dart';
const _dynamicIslandOpenDuration = Duration(milliseconds: 460);
const _dynamicIslandCloseDuration = Duration(milliseconds: 340);
const _dynamicIslandEaseOut = Cubic(0.16, 1, 0.3, 1);
class _DynamicIslandQrScannerPortal extends HookWidget {
const _DynamicIslandQrScannerPortal();
@override
Widget build(BuildContext context) {
final controller = useMemoized(MobileScannerController.new);
final animation = useAnimationController(
duration: _dynamicIslandOpenDuration,
reverseDuration: _dynamicIslandCloseDuration,
);
final isClosing = useState(false);
final canPop = useState(false);
final hasHandledResult = useRef(false);
final reduceMotion = MediaQuery.disableAnimationsOf(context);
useEffect(() {
unawaited(_setDynamicIslandScannerStatusBarHidden(true));
if (reduceMotion) {
animation.value = 1;
} else {
unawaited(animation.forward());
}
return () {
unawaited(_setDynamicIslandScannerStatusBarHidden(false));
unawaited(controller.dispose());
};
}, [animation, controller, reduceMotion]);
Future<void> finish(String? result) async {
canPop.value = true;
await WidgetsBinding.instance.endOfFrame;
if (context.mounted) {
Navigator.of(context).pop(result);
}
}
Future<void> closePortal() async {
if (isClosing.value || hasHandledResult.value) {
return;
}
hasHandledResult.value = true;
isClosing.value = true;
canPop.value = true;
if (reduceMotion) {
animation.value = 0;
await WidgetsBinding.instance.endOfFrame;
} else {
await animation.reverse();
}
if (context.mounted) {
Navigator.of(context).pop();
}
}
void handleDetection(BarcodeCapture capture) {
if (isClosing.value || hasHandledResult.value) {
return;
}
final value = _firstScannedValue(capture);
if (value == null) {
return;
}
hasHandledResult.value = true;
unawaited(_performDynamicIslandQrScanSuccessHaptic());
unawaited(finish(value));
}
return PopScope(
canPop: canPop.value,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) {
unawaited(closePortal());
}
},
child: Material(
type: MaterialType.transparency,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: closePortal,
child: LayoutBuilder(
builder: (context, constraints) {
final geometry = DynamicIslandQrScannerGeometry(
viewport: constraints.biggest,
safeAreaTop: MediaQuery.viewPaddingOf(context).top,
);
return Stack(
children: [
Positioned.fill(
child: AnimatedBuilder(
animation: animation,
builder: (context, _) => ColoredBox(
color: Colors.black.withValues(
alpha: 0.12 * animation.value,
),
),
),
),
AnimatedBuilder(
animation: animation,
builder: (context, _) {
final progress = _dynamicIslandEaseOut.transform(
animation.value.clamp(0.0, 1.0),
);
final frame = geometry.frameAt(progress);
final scannerOpacity = geometry.scannerOpacityAt(
progress,
);
final introLabelOpacity = isClosing.value
? 0.0
: geometry.introLabelOpacityAt(progress);
final promptOpacity = isClosing.value
? 0.0
: math.max(introLabelOpacity, scannerOpacity);
return Positioned.fromRect(
rect: frame,
child: ClipRRect(
key: const ValueKey(
'dynamic-island-qr-scanner-portal',
),
borderRadius: BorderRadius.circular(
geometry.cornerRadiusAt(progress),
),
child: ColoredBox(
color: Colors.black,
child: Stack(
fit: StackFit.expand,
children: [
if (!isClosing.value && scannerOpacity > 0)
Opacity(
opacity: scannerOpacity,
child: _QrScannerCamera(
controller: controller,
onDetect: handleDetection,
),
),
IgnorePointer(
child: Opacity(
opacity: promptOpacity,
child: Center(
child: Text(
context.l10n.scanQrCodeShort,
style: context.textTheme.bodyMedium
?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
),
),
);
},
),
],
);
},
),
),
),
);
}
}
@@ -0,0 +1,282 @@
part of '../pairing_qr_scanner.dart';
const _fallbackScannerOpenDuration = Duration(milliseconds: 420);
const _fallbackScannerCloseDuration = Duration(milliseconds: 320);
const _fallbackScannerSheetPeekHeight = 112.0;
const _fallbackScannerSheetCornerRadius = 32.0;
const _fallbackScannerDrawerCurve = Cubic(0.32, 0.72, 0, 1);
/// Reveals the scanner behind the current app surface on standard devices.
///
/// The app surface moves down as a rounded sheet instead of navigating to a
/// separate scanner page. This is used by Android and iPhones without a
/// Dynamic Island.
class FallbackPairingQrScanner extends HookWidget {
const FallbackPairingQrScanner({
required this.appSurface,
required this.onClosed,
super.key,
});
/// The current Buzz screen that moves down to reveal the camera.
final Widget appSurface;
/// Called after the app surface has returned, with a scanned value if any.
final ValueChanged<String?> onClosed;
@override
Widget build(BuildContext context) {
final controller = useMemoized(MobileScannerController.new);
final animation = useAnimationController(
duration: _fallbackScannerOpenDuration,
reverseDuration: _fallbackScannerCloseDuration,
);
final isClosing = useRef(false);
final reduceMotion = MediaQuery.disableAnimationsOf(context);
useEffect(
() => () {
unawaited(controller.dispose());
},
[controller],
);
useEffect(() {
if (reduceMotion) {
animation.value = 1;
} else {
unawaited(animation.forward());
}
return null;
}, [animation, reduceMotion]);
Future<void> closeScanner([String? result]) async {
if (isClosing.value) {
return;
}
isClosing.value = true;
if (reduceMotion) {
animation.value = 0;
await WidgetsBinding.instance.endOfFrame;
} else {
await animation.reverse();
}
if (context.mounted) {
onClosed(result);
}
}
void handleDetection(BarcodeCapture capture) {
if (isClosing.value) {
return;
}
final value = _firstScannedValue(capture);
if (value == null) {
return;
}
unawaited(closeScanner(value));
}
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (!didPop) {
unawaited(closeScanner());
}
},
child: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light,
child: Material(
key: const ValueKey('fallback-qr-scanner-reveal'),
color: Colors.black,
child: LayoutBuilder(
builder: (context, constraints) {
final viewport = constraints.biggest;
final sheetTravel = math.max(
0.0,
viewport.height - _fallbackScannerSheetPeekHeight,
);
return Stack(
clipBehavior: Clip.hardEdge,
children: [
Positioned.fill(
child: Semantics(
button: true,
label: context.l10n.closeQrScanner,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: closeScanner,
child: Stack(
fit: StackFit.expand,
children: [
_QrScannerCamera(
controller: controller,
onDetect: handleDetection,
),
const IgnorePointer(
child: _FallbackQrScannerViewfinder(),
),
],
),
),
),
),
AnimatedBuilder(
animation: animation,
child: appSurface,
builder: (context, child) {
final progress = _fallbackScannerDrawerCurve.transform(
animation.value.clamp(0.0, 1.0),
);
final offset = sheetTravel * progress;
final radius =
_fallbackScannerSheetCornerRadius * progress;
return Positioned(
top: offset,
left: 0,
right: 0,
height: viewport.height,
child: Semantics(
button: true,
label: context.l10n.closeQrScanner,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: closeScanner,
child: ClipRRect(
key: const ValueKey(
'fallback-qr-scanner-app-sheet',
),
borderRadius: BorderRadius.vertical(
top: Radius.circular(radius),
),
child: Stack(
fit: StackFit.expand,
children: [
AbsorbPointer(child: child),
if (progress > 0)
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(
top: Grid.twelve,
),
child: Opacity(
opacity: progress,
child: Container(
width: Grid.md,
height: Grid.half,
decoration: BoxDecoration(
color: context
.colors
.onSurfaceVariant
.withValues(alpha: 0.45),
borderRadius:
BorderRadius.circular(
Grid.half,
),
),
),
),
),
),
],
),
),
),
),
);
},
),
],
);
},
),
),
),
);
}
}
class _FallbackQrScannerViewfinder extends StatelessWidget {
const _FallbackQrScannerViewfinder();
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final size = math.min(
constraints.maxWidth - (Grid.xl * 2),
constraints.maxHeight - 240,
);
return Stack(
fit: StackFit.expand,
children: [
CustomPaint(
painter: _FallbackQrScannerViewfinderPainter(
viewfinderSize: math.max(0, size),
),
),
Align(
alignment: Alignment.center,
child: Transform.translate(
offset: Offset(0, (size / 2) + Grid.md),
child: Text(
context.l10n.scanQrCodeShort,
style: context.textTheme.bodyMedium?.copyWith(
color: Colors.white,
fontWeight: FontWeight.w600,
),
),
),
),
],
);
},
);
}
}
class _FallbackQrScannerViewfinderPainter extends CustomPainter {
const _FallbackQrScannerViewfinderPainter({required this.viewfinderSize});
final double viewfinderSize;
@override
void paint(Canvas canvas, Size size) {
final rect = Rect.fromCenter(
center: size.center(Offset.zero),
width: viewfinderSize,
height: viewfinderSize,
);
final roundedRect = RRect.fromRectAndRadius(
rect,
const Radius.circular(40),
);
canvas.saveLayer(Offset.zero & size, Paint());
canvas.drawRect(
Offset.zero & size,
Paint()..color = Colors.black.withValues(alpha: 0.6),
);
canvas.drawRRect(roundedRect, Paint()..blendMode = BlendMode.clear);
canvas.restore();
canvas.drawRRect(
roundedRect,
Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 3,
);
}
@override
bool shouldRepaint(_FallbackQrScannerViewfinderPainter oldDelegate) {
return oldDelegate.viewfinderSize != viewfinderSize;
}
}
@@ -0,0 +1,52 @@
part of '../pairing_qr_scanner.dart';
class _QrScannerCamera extends StatelessWidget {
const _QrScannerCamera({required this.controller, required this.onDetect});
final MobileScannerController controller;
final ValueChanged<BarcodeCapture> onDetect;
@override
Widget build(BuildContext context) {
return MobileScanner(
controller: controller,
fit: BoxFit.cover,
errorBuilder: (context, error) {
final message = switch (error.errorCode) {
MobileScannerErrorCode.permissionDenied =>
context.l10n.cameraPermissionRequired,
_ => context.l10n.cameraStartFailed(
error.errorDetails?.message ?? context.l10n.unknownError,
),
};
return ColoredBox(
color: Colors.black,
child: Center(
child: Padding(
padding: const EdgeInsets.all(Grid.sm),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LucideIcons.cameraOff,
size: 48,
color: Colors.white.withValues(alpha: 0.72),
),
const SizedBox(height: Grid.xs),
Text(
message,
textAlign: TextAlign.center,
style: context.textTheme.bodyMedium?.copyWith(
color: Colors.white.withValues(alpha: 0.72),
),
),
],
),
),
),
);
},
onDetect: onDetect,
);
}
}