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,176 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
import '../relay/relay.dart';
|
||||
|
||||
/// A circular avatar that supports both remote URLs and inline image data.
|
||||
///
|
||||
/// Flutter's [NetworkImage] only loads network URLs, while desktop browsers also
|
||||
/// accept `data:image/*` sources directly. Agent emoji avatars are inline SVGs,
|
||||
/// so mobile must decode those before rendering them.
|
||||
class AvatarImage extends StatelessWidget {
|
||||
final String? imageUrl;
|
||||
final double radius;
|
||||
final Color? backgroundColor;
|
||||
final Widget fallback;
|
||||
|
||||
const AvatarImage({
|
||||
super.key,
|
||||
required this.imageUrl,
|
||||
required this.radius,
|
||||
required this.fallback,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CircleAvatar(
|
||||
radius: radius,
|
||||
backgroundColor: backgroundColor,
|
||||
child: ClipOval(
|
||||
child: SizedBox.square(
|
||||
dimension: radius * 2,
|
||||
child: AvatarImageContent(imageUrl: imageUrl, fallback: fallback),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Image content for avatar surfaces whose shape is supplied by their parent.
|
||||
class AvatarImageContent extends StatefulWidget {
|
||||
final String? imageUrl;
|
||||
final Widget fallback;
|
||||
final BoxFit fit;
|
||||
|
||||
const AvatarImageContent({
|
||||
super.key,
|
||||
required this.imageUrl,
|
||||
required this.fallback,
|
||||
this.fit = BoxFit.cover,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AvatarImageContent> createState() => _AvatarImageContentState();
|
||||
}
|
||||
|
||||
class _AvatarImageContentState extends State<AvatarImageContent> {
|
||||
late _AvatarSource? _source = _AvatarSource.parse(widget.imageUrl);
|
||||
|
||||
@override
|
||||
void didUpdateWidget(AvatarImageContent oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.imageUrl != oldWidget.imageUrl) {
|
||||
_source = _AvatarSource.parse(widget.imageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final centeredFallback = Center(child: widget.fallback);
|
||||
|
||||
return switch (_source) {
|
||||
_EmojiAvatarSource(:final emoji, :final color) => ColoredBox(
|
||||
color: color,
|
||||
child: LayoutBuilder(
|
||||
builder: (_, constraints) => Center(
|
||||
child: Text(
|
||||
emoji,
|
||||
textScaler: TextScaler.noScaling,
|
||||
style: TextStyle(
|
||||
fontSize: constraints.biggest.shortestSide * 258 / 512,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_SvgAvatarSource(:final svg) => SvgPicture.string(
|
||||
svg,
|
||||
fit: widget.fit,
|
||||
placeholderBuilder: (_) => centeredFallback,
|
||||
errorBuilder: (_, _, _) => centeredFallback,
|
||||
),
|
||||
_RasterDataAvatarSource(:final bytes) => Image.memory(
|
||||
bytes,
|
||||
fit: widget.fit,
|
||||
errorBuilder: (_, _, _) => centeredFallback,
|
||||
),
|
||||
_NetworkAvatarSource(:final url) => MediaImage(
|
||||
url: url,
|
||||
fit: widget.fit,
|
||||
errorBuilder: (_, _, _) => centeredFallback,
|
||||
),
|
||||
null => centeredFallback,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sealed class _AvatarSource {
|
||||
const _AvatarSource();
|
||||
|
||||
static _AvatarSource? parse(String? value) {
|
||||
final url = value?.trim();
|
||||
if (url == null || url.isEmpty) return null;
|
||||
if (!url.startsWith('data:image/')) return _NetworkAvatarSource(url);
|
||||
|
||||
try {
|
||||
final data = UriData.parse(url);
|
||||
if (data.mimeType == 'image/svg+xml') {
|
||||
final Uint8List bytes = data.contentAsBytes();
|
||||
final svg = utf8.decode(bytes);
|
||||
return _parseEmojiAvatar(svg) ?? _SvgAvatarSource(svg);
|
||||
}
|
||||
return _RasterDataAvatarSource(data.contentAsBytes());
|
||||
} on FormatException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_EmojiAvatarSource? _parseEmojiAvatar(String svg) {
|
||||
final colorValue = RegExp(
|
||||
r'<rect\b[^>]*\sfill="([^"]+)"',
|
||||
).firstMatch(svg)?[1];
|
||||
final emojiValue = RegExp(r'<text\b[^>]*>(.*?)</text>').firstMatch(svg)?[1];
|
||||
if (colorValue == null || emojiValue == null) return null;
|
||||
|
||||
final color = _parseHexColor(colorValue);
|
||||
if (color == null) return null;
|
||||
final emoji = emojiValue
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('&', '&');
|
||||
return _EmojiAvatarSource(emoji, color);
|
||||
}
|
||||
|
||||
Color? _parseHexColor(String value) {
|
||||
final hex = value.startsWith('#') ? value.substring(1) : value;
|
||||
if (!RegExp(r'^[0-9a-fA-F]{6}$').hasMatch(hex)) return null;
|
||||
final rgb = int.tryParse(hex, radix: 16);
|
||||
return rgb == null ? null : Color(0xFF000000 | rgb);
|
||||
}
|
||||
|
||||
class _EmojiAvatarSource extends _AvatarSource {
|
||||
final String emoji;
|
||||
final Color color;
|
||||
const _EmojiAvatarSource(this.emoji, this.color);
|
||||
}
|
||||
|
||||
class _SvgAvatarSource extends _AvatarSource {
|
||||
final String svg;
|
||||
const _SvgAvatarSource(this.svg);
|
||||
}
|
||||
|
||||
class _RasterDataAvatarSource extends _AvatarSource {
|
||||
final Uint8List bytes;
|
||||
const _RasterDataAvatarSource(this.bytes);
|
||||
}
|
||||
|
||||
class _NetworkAvatarSource extends _AvatarSource {
|
||||
final String url;
|
||||
const _NetworkAvatarSource(this.url);
|
||||
}
|
||||
Reference in New Issue
Block a user