Files
slogan/app/lib/features/profile/avatar_provider.dart
T
admin b53c57e02d Add 'app/' from commit 'a11a66886941bba128d89c1124115e1b1c87a128'
git-subtree-dir: app
git-subtree-mainline: 6ebd902c6b
git-subtree-split: a11a668869
2026-08-04 14:59:55 +08:00

85 lines
2.6 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/auth/auth_provider.dart';
class AvatarState {
final int faceTemplateId;
final int bodyTemplateId;
final int skinToneIndex;
final String glbUrl;
final String framesUrl;
final String buildStatus; // pending / processing / done / failed
final String error;
const AvatarState({
this.faceTemplateId = 0,
this.bodyTemplateId = 0,
this.skinToneIndex = 0,
this.glbUrl = '',
this.framesUrl = '',
this.buildStatus = '',
this.error = '',
});
bool get built => buildStatus == 'done' && glbUrl.isNotEmpty;
}
class AvatarNotifier extends AsyncNotifier<AvatarState> {
@override
Future<AvatarState> build() async {
final api = ref.read(apiClientProvider);
final data = await api.get<Map<String, dynamic>>('/avatar/get');
return _fromData(data);
}
AvatarState _fromData(Map<String, dynamic> data) => AvatarState(
faceTemplateId: (data['face_template_id'] as num?)?.toInt() ?? 0,
bodyTemplateId: (data['body_template_id'] as num?)?.toInt() ?? 0,
skinToneIndex: (data['skin_tone_index'] as num?)?.toInt() ?? 0,
glbUrl: data['glb_url'] as String? ?? '',
framesUrl: data['frames_url'] as String? ?? '',
buildStatus: data['build_status'] as String? ?? '',
error: data['error'] as String? ?? '',
);
Future<void> refresh() async {
state = const AsyncLoading();
state = await AsyncValue.guard(build);
}
/// 触发构建;若服务端异步则轮询直到 done/failed
Future<void> buildAvatar() async {
state = const AsyncLoading();
try {
final api = ref.read(apiClientProvider);
final data =
await api.post<Map<String, dynamic>>('/avatar/build', {});
final status = data['status'] as String? ?? '';
if (status == 'pending' || status == 'processing') {
await _poll();
} else {
await refresh();
}
} catch (e) {
state = AsyncError(e, StackTrace.current);
}
}
Future<void> _poll() async {
for (var i = 0; i < 30; i++) {
await Future.delayed(const Duration(seconds: 2));
final api = ref.read(apiClientProvider);
final data = await api.get<Map<String, dynamic>>('/avatar/get');
final status = data['build_status'] as String? ?? '';
if (status == 'done' || status == 'failed') {
state = AsyncData(_fromData(data));
return;
}
}
state = AsyncError(StateError('化身构建超时,请稍后重试'), StackTrace.empty);
}
}
final avatarProvider =
AsyncNotifierProvider<AvatarNotifier, AvatarState>(AvatarNotifier.new);