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:
hailin 2025-12-08 19:04:13 -08:00
parent 3e01f69044
commit 8eefd807c3
2 changed files with 89 additions and 29 deletions

View File

@ -36,17 +36,40 @@ class _MiningPageState extends ConsumerState<MiningPage> {
@override
void initState() {
super.initState();
//
_checkLocalAvatarSync();
_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 {
final accountService = ref.read(accountServiceProvider);
final serialNum = await accountService.getUserSerialNum();
final avatarSvg = await accountService.getAvatarSvg();
final avatarUrl = await accountService.getAvatarUrl();
final localAvatarPath = await accountService.getLocalAvatarPath();
//
final results = await Future.wait([
accountService.getUserSerialNum(),
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) {
setState(() {
@ -386,16 +409,22 @@ class _MiningPageState extends ConsumerState<MiningPage> {
// 1.
if (_localAvatarPath != null && _localAvatarPath!.isNotEmpty) {
final file = File(_localAvatarPath!);
return Image.file(
file,
width: 80,
height: 80,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
// URL
return _buildNetworkOrSvgAvatar();
},
);
//
if (file.existsSync()) {
return Image.file(
file,
width: 80,
height: 80,
fit: BoxFit.cover,
cacheWidth: 160, // 2x
cacheHeight: 160,
gaplessPlayback: true, //
errorBuilder: (context, error, stackTrace) {
// URL
return _buildNetworkOrSvgAvatar();
},
);
}
}
// 2. URL或SVG

View File

@ -64,18 +64,43 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
void initState() {
super.initState();
_startCountdown();
//
_checkLocalAvatarSync();
_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 {
final accountService = ref.read(accountServiceProvider);
final username = await accountService.getUsername();
final serialNum = await accountService.getUserSerialNum();
final avatarSvg = await accountService.getAvatarSvg();
final avatarUrl = await accountService.getAvatarUrl();
final localAvatarPath = await accountService.getLocalAvatarPath();
//
final results = await Future.wait([
accountService.getUsername(),
accountService.getUserSerialNum(),
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) {
setState(() {
@ -365,16 +390,22 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
// 1.
if (_localAvatarPath != null && _localAvatarPath!.isNotEmpty) {
final file = File(_localAvatarPath!);
return Image.file(
file,
width: 80,
height: 80,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
// URL
return _buildNetworkOrSvgAvatar();
},
);
//
if (file.existsSync()) {
return Image.file(
file,
width: 80,
height: 80,
fit: BoxFit.cover,
cacheWidth: 160, // 2x
cacheHeight: 160,
gaplessPlayback: true, //
errorBuilder: (context, error, stackTrace) {
// URL
return _buildNetworkOrSvgAvatar();
},
);
}
}
// 2. URL或SVG