git-subtree-dir: app git-subtree-mainline:6ebd902c6bgit-subtree-split:a11a668869
85 lines
2.6 KiB
Dart
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);
|