Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../api/oa_client.dart';
|
||||
import '../theme.dart';
|
||||
|
||||
class AuthImage extends StatefulWidget {
|
||||
const AuthImage({
|
||||
super.key,
|
||||
required this.api,
|
||||
required this.fileId,
|
||||
this.width,
|
||||
this.height,
|
||||
this.fit = BoxFit.cover,
|
||||
this.placeholder,
|
||||
});
|
||||
final OaClient api;
|
||||
final String fileId;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final BoxFit fit;
|
||||
final Widget? placeholder;
|
||||
|
||||
@override
|
||||
State<AuthImage> createState() => _AuthImageState();
|
||||
}
|
||||
|
||||
class _AuthImageState extends State<AuthImage> {
|
||||
Uint8List? _bytes;
|
||||
bool _fail = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_bytes = widget.api.peekFile(widget.fileId);
|
||||
if (_bytes == null) _load();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant AuthImage oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.fileId != widget.fileId) {
|
||||
_bytes = widget.api.peekFile(widget.fileId);
|
||||
_fail = false;
|
||||
if (_bytes == null) _load();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final b = await widget.api.fileBytes(widget.fileId);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_bytes = b;
|
||||
_fail = b == null;
|
||||
});
|
||||
} catch (_) {
|
||||
if (mounted) setState(() => _fail = true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_bytes != null) {
|
||||
return Image.memory(
|
||||
_bytes!,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: widget.fit,
|
||||
gaplessPlayback: true,
|
||||
filterQuality: FilterQuality.medium,
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
child: widget.placeholder ??
|
||||
ColoredBox(
|
||||
color: const Color(0xFFEDEDED),
|
||||
child: _fail ? const Icon(Icons.broken_image_outlined, color: kMute, size: 18) : const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user