perf(mobile-app): optimize avatar loading with parallel fetch and file caching
- Use Future.wait for parallel data loading (faster initial load) - Add file.existsSync() check before using Image.file - Add cacheWidth/cacheHeight and gaplessPlayback for smoother display - Add _checkLocalAvatarSync for early avatar path detection 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3e01f69044
commit
8eefd807c3
|
|
@ -36,17 +36,40 @@ class _MiningPageState extends ConsumerState<MiningPage> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// 先同步检查本地头像,再异步加载其他数据
|
||||||
|
_checkLocalAvatarSync();
|
||||||
_loadUserData();
|
_loadUserData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 同步检查本地头像文件(在 build 之前快速获取)
|
||||||
|
void _checkLocalAvatarSync() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
final accountService = ref.read(accountServiceProvider);
|
||||||
|
final localPath = await accountService.getLocalAvatarPath();
|
||||||
|
if (mounted && localPath != null && _localAvatarPath == null) {
|
||||||
|
setState(() {
|
||||||
|
_localAvatarPath = localPath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// 加载用户数据
|
/// 加载用户数据
|
||||||
Future<void> _loadUserData() async {
|
Future<void> _loadUserData() async {
|
||||||
final accountService = ref.read(accountServiceProvider);
|
final accountService = ref.read(accountServiceProvider);
|
||||||
|
|
||||||
final serialNum = await accountService.getUserSerialNum();
|
// 并行加载所有数据
|
||||||
final avatarSvg = await accountService.getAvatarSvg();
|
final results = await Future.wait([
|
||||||
final avatarUrl = await accountService.getAvatarUrl();
|
accountService.getUserSerialNum(),
|
||||||
final localAvatarPath = await accountService.getLocalAvatarPath();
|
accountService.getAvatarSvg(),
|
||||||
|
accountService.getAvatarUrl(),
|
||||||
|
accountService.getLocalAvatarPath(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final serialNum = results[0] as int?;
|
||||||
|
final avatarSvg = results[1] as String?;
|
||||||
|
final avatarUrl = results[2] as String?;
|
||||||
|
final localAvatarPath = results[3] as String?;
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -386,16 +409,22 @@ class _MiningPageState extends ConsumerState<MiningPage> {
|
||||||
// 1. 优先显示本地缓存的头像文件
|
// 1. 优先显示本地缓存的头像文件
|
||||||
if (_localAvatarPath != null && _localAvatarPath!.isNotEmpty) {
|
if (_localAvatarPath != null && _localAvatarPath!.isNotEmpty) {
|
||||||
final file = File(_localAvatarPath!);
|
final file = File(_localAvatarPath!);
|
||||||
return Image.file(
|
// 同步检查文件是否存在
|
||||||
file,
|
if (file.existsSync()) {
|
||||||
width: 80,
|
return Image.file(
|
||||||
height: 80,
|
file,
|
||||||
fit: BoxFit.cover,
|
width: 80,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
height: 80,
|
||||||
// 本地文件加载失败,尝试网络URL
|
fit: BoxFit.cover,
|
||||||
return _buildNetworkOrSvgAvatar();
|
cacheWidth: 160, // 2x 分辨率缓存,提升性能
|
||||||
},
|
cacheHeight: 160,
|
||||||
);
|
gaplessPlayback: true, // 防止图片切换时闪烁
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
// 本地文件加载失败,尝试网络URL
|
||||||
|
return _buildNetworkOrSvgAvatar();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 没有本地缓存,尝试网络URL或SVG
|
// 2. 没有本地缓存,尝试网络URL或SVG
|
||||||
|
|
|
||||||
|
|
@ -64,18 +64,43 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_startCountdown();
|
_startCountdown();
|
||||||
|
// 先同步检查本地头像,再异步加载其他数据
|
||||||
|
_checkLocalAvatarSync();
|
||||||
_loadUserData();
|
_loadUserData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 同步检查本地头像文件(在 build 之前快速获取)
|
||||||
|
void _checkLocalAvatarSync() {
|
||||||
|
// 使用 WidgetsBinding 确保在第一帧渲染前执行
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
final accountService = ref.read(accountServiceProvider);
|
||||||
|
final localPath = await accountService.getLocalAvatarPath();
|
||||||
|
if (mounted && localPath != null && _localAvatarPath == null) {
|
||||||
|
setState(() {
|
||||||
|
_localAvatarPath = localPath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// 加载用户数据
|
/// 加载用户数据
|
||||||
Future<void> _loadUserData() async {
|
Future<void> _loadUserData() async {
|
||||||
final accountService = ref.read(accountServiceProvider);
|
final accountService = ref.read(accountServiceProvider);
|
||||||
|
|
||||||
final username = await accountService.getUsername();
|
// 并行加载所有数据
|
||||||
final serialNum = await accountService.getUserSerialNum();
|
final results = await Future.wait([
|
||||||
final avatarSvg = await accountService.getAvatarSvg();
|
accountService.getUsername(),
|
||||||
final avatarUrl = await accountService.getAvatarUrl();
|
accountService.getUserSerialNum(),
|
||||||
final localAvatarPath = await accountService.getLocalAvatarPath();
|
accountService.getAvatarSvg(),
|
||||||
|
accountService.getAvatarUrl(),
|
||||||
|
accountService.getLocalAvatarPath(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final username = results[0] as String?;
|
||||||
|
final serialNum = results[1] as int?;
|
||||||
|
final avatarSvg = results[2] as String?;
|
||||||
|
final avatarUrl = results[3] as String?;
|
||||||
|
final localAvatarPath = results[4] as String?;
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -365,16 +390,22 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
|
||||||
// 1. 优先显示本地缓存的头像文件
|
// 1. 优先显示本地缓存的头像文件
|
||||||
if (_localAvatarPath != null && _localAvatarPath!.isNotEmpty) {
|
if (_localAvatarPath != null && _localAvatarPath!.isNotEmpty) {
|
||||||
final file = File(_localAvatarPath!);
|
final file = File(_localAvatarPath!);
|
||||||
return Image.file(
|
// 同步检查文件是否存在
|
||||||
file,
|
if (file.existsSync()) {
|
||||||
width: 80,
|
return Image.file(
|
||||||
height: 80,
|
file,
|
||||||
fit: BoxFit.cover,
|
width: 80,
|
||||||
errorBuilder: (context, error, stackTrace) {
|
height: 80,
|
||||||
// 本地文件加载失败,尝试网络URL
|
fit: BoxFit.cover,
|
||||||
return _buildNetworkOrSvgAvatar();
|
cacheWidth: 160, // 2x 分辨率缓存,提升性能
|
||||||
},
|
cacheHeight: 160,
|
||||||
);
|
gaplessPlayback: true, // 防止图片切换时闪烁
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
// 本地文件加载失败,尝试网络URL
|
||||||
|
return _buildNetworkOrSvgAvatar();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 没有本地缓存,尝试网络URL或SVG
|
// 2. 没有本地缓存,尝试网络URL或SVG
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue