2026-04-06 01:28:10 +08:00
|
|
|
import 'package:dio/dio.dart';
|
|
|
|
|
|
|
|
|
|
import '../../../../core/network/api_problem.dart';
|
|
|
|
|
import '../../../../data/network/api_client.dart';
|
|
|
|
|
import '../models/profile_settings.dart';
|
|
|
|
|
|
|
|
|
|
class ProfileApi {
|
|
|
|
|
const ProfileApi({required ApiClient apiClient}) : _apiClient = apiClient;
|
|
|
|
|
|
|
|
|
|
final ApiClient _apiClient;
|
|
|
|
|
|
|
|
|
|
Future<ProfileSettingsV1> getProfile() async {
|
|
|
|
|
final json = await _apiClient.getJson('/api/v1/users/me/profile');
|
|
|
|
|
return _toSettings(json);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<ProfileSettingsV1> updateProfile(ProfileSettingsV1 next) async {
|
|
|
|
|
final payload = <String, dynamic>{
|
|
|
|
|
'display_name': next.displayName,
|
|
|
|
|
'bio': next.bio,
|
|
|
|
|
if (next.avatarPath != null && next.avatarPath!.isNotEmpty)
|
|
|
|
|
'avatar_path': next.avatarPath,
|
|
|
|
|
};
|
|
|
|
|
final json = await _apiClient.rawDio.patch<Map<String, dynamic>>(
|
|
|
|
|
'/api/v1/users/me/profile',
|
|
|
|
|
data: payload,
|
|
|
|
|
);
|
|
|
|
|
final data = json.data;
|
|
|
|
|
if (data is! Map<String, dynamic>) {
|
|
|
|
|
throw ApiProblem(
|
|
|
|
|
status: 502,
|
|
|
|
|
title: 'Invalid profile payload',
|
|
|
|
|
detail: 'Expected profile response object',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return _toSettings(data);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 18:43:42 +08:00
|
|
|
Future<ProfileSettingsV1> updateSettings(ProfileSettingsV1 settings) async {
|
|
|
|
|
final payload = <String, dynamic>{
|
|
|
|
|
'settings': {
|
|
|
|
|
'version': settings.version,
|
|
|
|
|
'preferences': {
|
|
|
|
|
'interface_language': settings.preferences.interfaceLanguage,
|
|
|
|
|
'ai_language': settings.preferences.aiLanguage,
|
|
|
|
|
'timezone': settings.preferences.timezone,
|
|
|
|
|
'country': settings.preferences.country,
|
|
|
|
|
},
|
|
|
|
|
'privacy': settings.privacy,
|
|
|
|
|
'notification': {
|
|
|
|
|
'allow_notifications': settings.notification.allowNotifications,
|
|
|
|
|
'allow_vibration': settings.notification.allowVibration,
|
|
|
|
|
},
|
2026-04-15 18:56:41 +08:00
|
|
|
'divination_tutorial': {
|
|
|
|
|
'divination_entry_shown':
|
|
|
|
|
settings.divinationTutorial.divinationEntryShown,
|
|
|
|
|
'auto_divination_shown':
|
|
|
|
|
settings.divinationTutorial.autoDivinationShown,
|
|
|
|
|
'manual_divination_shown':
|
|
|
|
|
settings.divinationTutorial.manualDivinationShown,
|
|
|
|
|
},
|
2026-04-07 18:43:42 +08:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
final json = await _apiClient.rawDio.patch<Map<String, dynamic>>(
|
|
|
|
|
'/api/v1/users/me/settings',
|
|
|
|
|
data: payload,
|
|
|
|
|
);
|
|
|
|
|
final data = json.data;
|
|
|
|
|
if (data is! Map<String, dynamic>) {
|
|
|
|
|
throw ApiProblem(
|
|
|
|
|
status: 502,
|
|
|
|
|
title: 'Invalid settings payload',
|
|
|
|
|
detail: 'Expected settings response object',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return _toSettings(data);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 01:28:10 +08:00
|
|
|
Future<ProfileSettingsV1> uploadAvatar(String filePath) async {
|
|
|
|
|
final formData = FormData.fromMap({
|
|
|
|
|
'file': await MultipartFile.fromFile(filePath),
|
|
|
|
|
});
|
|
|
|
|
final response = await _apiClient.rawDio.post<Map<String, dynamic>>(
|
|
|
|
|
'/api/v1/users/me/avatar',
|
|
|
|
|
data: formData,
|
|
|
|
|
);
|
|
|
|
|
final data = response.data;
|
|
|
|
|
if (data is! Map<String, dynamic>) {
|
|
|
|
|
throw ApiProblem(
|
|
|
|
|
status: 502,
|
|
|
|
|
title: 'Invalid profile payload',
|
|
|
|
|
detail: 'Expected profile response object',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return _toSettings(data);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 10:40:44 +08:00
|
|
|
Future<void> deleteAccount() async {
|
|
|
|
|
await _apiClient.deleteNoContent('/api/v1/users/me');
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 01:28:10 +08:00
|
|
|
ProfileSettingsV1 _toSettings(Map<String, dynamic> json) {
|
|
|
|
|
final settingsRaw = json['settings'];
|
|
|
|
|
final preferencesRaw = settingsRaw is Map<String, dynamic>
|
|
|
|
|
? settingsRaw['preferences']
|
|
|
|
|
: null;
|
|
|
|
|
final preferences = preferencesRaw is Map<String, dynamic>
|
|
|
|
|
? PreferenceSettings(
|
|
|
|
|
interfaceLanguage:
|
|
|
|
|
(preferencesRaw['interface_language'] as String?) ?? 'zh-CN',
|
|
|
|
|
aiLanguage: (preferencesRaw['ai_language'] as String?) ?? 'zh-CN',
|
|
|
|
|
timezone:
|
|
|
|
|
(preferencesRaw['timezone'] as String?) ?? 'Asia/Shanghai',
|
2026-04-16 16:11:09 +08:00
|
|
|
country: (preferencesRaw['country'] as String?) ?? 'US',
|
2026-04-06 01:28:10 +08:00
|
|
|
)
|
|
|
|
|
: const PreferenceSettings();
|
|
|
|
|
|
2026-04-07 18:43:42 +08:00
|
|
|
final notificationRaw = settingsRaw is Map<String, dynamic>
|
|
|
|
|
? settingsRaw['notification']
|
|
|
|
|
: null;
|
|
|
|
|
final notification = notificationRaw is Map<String, dynamic>
|
|
|
|
|
? NotificationSettings(
|
|
|
|
|
allowNotifications:
|
|
|
|
|
(notificationRaw['allow_notifications'] as bool? ?? true),
|
|
|
|
|
allowVibration:
|
|
|
|
|
(notificationRaw['allow_vibration'] as bool? ?? true),
|
|
|
|
|
)
|
|
|
|
|
: const NotificationSettings();
|
|
|
|
|
|
2026-04-15 18:56:41 +08:00
|
|
|
final divinationTutorialRaw = settingsRaw is Map<String, dynamic>
|
|
|
|
|
? settingsRaw['divination_tutorial']
|
|
|
|
|
: null;
|
|
|
|
|
final divinationTutorial = divinationTutorialRaw is Map<String, dynamic>
|
|
|
|
|
? DivinationTutorialSettings(
|
|
|
|
|
divinationEntryShown:
|
|
|
|
|
(divinationTutorialRaw['divination_entry_shown'] as bool?) ??
|
|
|
|
|
true,
|
|
|
|
|
autoDivinationShown:
|
|
|
|
|
(divinationTutorialRaw['auto_divination_shown'] as bool?) ??
|
|
|
|
|
true,
|
|
|
|
|
manualDivinationShown:
|
|
|
|
|
(divinationTutorialRaw['manual_divination_shown'] as bool?) ??
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
: const DivinationTutorialSettings();
|
|
|
|
|
|
2026-04-06 01:28:10 +08:00
|
|
|
return ProfileSettingsV1(
|
|
|
|
|
displayName: (json['display_name'] as String?) ?? '',
|
|
|
|
|
bio: (json['bio'] as String?) ?? '',
|
|
|
|
|
avatarPath: json['avatar_path'] as String?,
|
|
|
|
|
avatarUrl: json['avatar_url'] as String?,
|
|
|
|
|
preferences: preferences,
|
|
|
|
|
privacy: settingsRaw is Map<String, dynamic>
|
|
|
|
|
? (settingsRaw['privacy'] as Map<String, dynamic>? ??
|
|
|
|
|
const <String, dynamic>{})
|
|
|
|
|
: const <String, dynamic>{},
|
2026-04-07 18:43:42 +08:00
|
|
|
notification: notification,
|
2026-04-15 18:56:41 +08:00
|
|
|
divinationTutorial: divinationTutorial,
|
2026-04-06 01:28:10 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|