261 lines
7.8 KiB
Dart
261 lines
7.8 KiB
Dart
import 'dart:convert';
|
||
import 'dart:typed_data'; // 确保导入,因为 uploadStegImage 需要 Uint8List
|
||
import 'package:flutter/material.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import '../model/user_model.dart';
|
||
import '../service/user_service.dart';
|
||
import '../tool/encryption_util.dart';
|
||
|
||
class UserProvider with ChangeNotifier {
|
||
UserModel? _user;
|
||
String? _token;
|
||
List<String> _sexTags = [];
|
||
|
||
// --- 成绩数据源相关状态 ---
|
||
String _scoreDataSource = 'cn'; // 'cn' or 'sega'
|
||
String? _selectedSegaId; // 选中的 SegaID
|
||
|
||
// --- 新增:国服多账号支持 ---
|
||
String? _selectedCnUserName; // 选中的国服用户名 (对应 userName2userId 的 key)
|
||
|
||
UserModel? get user => _user;
|
||
String? get token => _token;
|
||
bool get isLogin => _token != null && _user != null;
|
||
String get username => _user?.name ?? "未登录";
|
||
List<String> get sexTags => _sexTags;
|
||
|
||
String get scoreDataSource => _scoreDataSource;
|
||
String? get selectedSegaId => _selectedSegaId;
|
||
String? get selectedCnUserName => _selectedCnUserName;
|
||
|
||
List<SegaCard> get availableSegaCards => _user?.segaCards ?? [];
|
||
|
||
// 获取可用的国服账号列表
|
||
List<String> get availableCnUserNames {
|
||
final map = _user?.userName2userId;
|
||
if (map == null || map.isEmpty) return [];
|
||
return map.keys.toList();
|
||
}
|
||
|
||
String get avatarUrl {
|
||
if (_user?.iconId != null) {
|
||
return "https://cdn.godserver.cn/resource/static/coll/Icon/UI_Icon_${_user!.iconId!}.png";
|
||
} else {
|
||
return "https://cdn.godserver.cn/resource/static/coll/Icon/UI_Icon_605526.png";
|
||
}
|
||
}
|
||
|
||
static UserProvider? _instance;
|
||
static UserProvider get instance {
|
||
_instance ??= UserProvider();
|
||
return _instance!;
|
||
}
|
||
|
||
Future<void> initUser() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
_token = prefs.getString("token");
|
||
|
||
// 读取偏好设置
|
||
_scoreDataSource = prefs.getString("scoreDataSource") ?? 'cn';
|
||
_selectedSegaId = prefs.getString("selectedSegaId");
|
||
_selectedCnUserName = prefs.getString("selectedCnUserName");
|
||
|
||
if (_token != null) {
|
||
try {
|
||
_user = await UserService.getUserInfo(_token!);
|
||
await fetchSexTags();
|
||
|
||
// 校验 SegaID 有效性
|
||
if (_selectedSegaId != null && !availableSegaCards.any((c) => c.segaId == _selectedSegaId)) {
|
||
_selectedSegaId = null;
|
||
await prefs.remove("selectedSegaId");
|
||
}
|
||
|
||
// 校验国服用户名有效性
|
||
if (_selectedCnUserName != null && !availableCnUserNames.contains(_selectedCnUserName)) {
|
||
_selectedCnUserName = null;
|
||
await prefs.remove("selectedCnUserName");
|
||
}
|
||
} catch (e) {
|
||
await logout();
|
||
}
|
||
}
|
||
notifyListeners();
|
||
}
|
||
|
||
Future<void> login(String username, String twoKeyCode) async {
|
||
try {
|
||
_token = await UserService.login(username, twoKeyCode);
|
||
if (_token != null && _token!.isNotEmpty) {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString("token", _token!);
|
||
_user = await UserService.getUserInfo(_token!);
|
||
await fetchSexTags();
|
||
}
|
||
} catch (e) {
|
||
rethrow;
|
||
} finally {
|
||
notifyListeners();
|
||
}
|
||
}
|
||
|
||
Future<void> register(String username, String password, String inviter) async {
|
||
await UserService.register(username, password, inviter);
|
||
// 注意:注册后通常不直接登录,或者根据业务需求决定。这里保持原逻辑。
|
||
// await login(username, password);
|
||
}
|
||
|
||
Future<void> logout() async {
|
||
_token = null;
|
||
_user = null;
|
||
_selectedSegaId = null;
|
||
_selectedCnUserName = null;
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.remove("token");
|
||
await prefs.remove("selectedSegaId");
|
||
await prefs.remove("selectedCnUserName");
|
||
notifyListeners();
|
||
}
|
||
|
||
Future<void> saveUserInfo() async {
|
||
if (_token == null || _user == null) return;
|
||
try {
|
||
final userJson = _user!.toJson();
|
||
final encrypted = EncryptionUtil.encryptAndCompress(jsonEncode(userJson));
|
||
await UserService.updateUser(_token!, encrypted);
|
||
_user = await UserService.getUserInfo(_token!);
|
||
notifyListeners();
|
||
} catch (e) {
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Future<bool> refreshToken() async {
|
||
if (_token == null) return false;
|
||
try {
|
||
final newToken = await UserService.refreshToken(_token!);
|
||
if (newToken != null) {
|
||
_token = newToken;
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString("token", newToken);
|
||
notifyListeners();
|
||
return true;
|
||
}
|
||
} catch (e) {
|
||
await logout();
|
||
}
|
||
return false;
|
||
}
|
||
|
||
Future<Map<String, dynamic>> fetchRadarData(String id) async {
|
||
if (_token == null) throw "请先登录";
|
||
try {
|
||
return await UserService.getRadarData(_token!, id);
|
||
} catch (e) {
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
Future<void> fetchSexTags() async {
|
||
_sexTags = await UserService.fetchSexTags();
|
||
notifyListeners();
|
||
}
|
||
|
||
void updateUser(UserModel? newUser) {
|
||
_user = newUser;
|
||
notifyListeners();
|
||
}
|
||
|
||
// --- 设置数据源 ---
|
||
Future<void> setScoreDataSource(String source) async {
|
||
_scoreDataSource = source;
|
||
final prefs = await SharedPreferences.getInstance();
|
||
await prefs.setString("scoreDataSource", source);
|
||
notifyListeners();
|
||
}
|
||
|
||
// --- 设置选中的 SegaID ---
|
||
Future<void> setSelectedSegaId(String? segaId) async {
|
||
_selectedSegaId = segaId;
|
||
final prefs = await SharedPreferences.getInstance();
|
||
if (segaId != null) {
|
||
await prefs.setString("selectedSegaId", segaId);
|
||
} else {
|
||
await prefs.remove("selectedSegaId");
|
||
}
|
||
notifyListeners();
|
||
}
|
||
|
||
// --- 设置选中的国服用户名 ---
|
||
Future<void> setSelectedCnUserName(String? userName) async {
|
||
_selectedCnUserName = userName;
|
||
final prefs = await SharedPreferences.getInstance();
|
||
if (userName != null && userName.isNotEmpty) {
|
||
await prefs.setString("selectedCnUserName", userName);
|
||
} else {
|
||
await prefs.remove("selectedCnUserName");
|
||
}
|
||
notifyListeners();
|
||
}
|
||
|
||
Future<void> syncSegaScore(Map<String, dynamic> data) async {
|
||
if (_token == null || _user == null) {
|
||
throw "请先登录";
|
||
}
|
||
if (_selectedSegaId == null || _selectedSegaId!.isEmpty) {
|
||
throw "请先选择要同步的 SegaID";
|
||
}
|
||
|
||
try {
|
||
final result = await UserService.uploadSegaRating(
|
||
_token!,
|
||
_selectedSegaId!,
|
||
data,
|
||
);
|
||
|
||
if (result["code"] == 200) {
|
||
print("✅ 同步成功:${result["msg"]}");
|
||
} else {
|
||
print("❌ 同步失败:${result["msg"]}");
|
||
throw result["msg"] ?? "同步失败";
|
||
}
|
||
} catch (e) {
|
||
print("❌ 同步异常:$e");
|
||
rethrow;
|
||
}
|
||
}
|
||
|
||
// ================= 新增:上传隐写图片 =================
|
||
|
||
/// 上传带有隐写数据的图片
|
||
/// [imageBytes] 原始图片的字节数据 (Uint8List)
|
||
/// [segaId] 要隐藏进图片的 SegaID (如果为 null,则使用当前选中的 _selectedSegaId)
|
||
Future<Map<String, dynamic>> uploadStegImage(Uint8List imageBytes, {String? segaId}) async {
|
||
if (_token == null) {
|
||
throw Exception("请先登录");
|
||
}
|
||
|
||
// 如果未指定 segaId,则使用当前选中的
|
||
final targetSegaId = segaId ?? _selectedSegaId;
|
||
|
||
if (targetSegaId == null || targetSegaId.isEmpty) {
|
||
throw Exception("请提供或选择要隐藏的 SegaID");
|
||
}
|
||
|
||
try {
|
||
print("🚀 开始隐写并上传... SegaID: $targetSegaId");
|
||
|
||
final result = await UserService.uploadStegImage(
|
||
token: _token!,
|
||
segaId: targetSegaId,
|
||
originalImageBytes: imageBytes,
|
||
);
|
||
|
||
print("✅ 隐写上传成功: ${result['msg']}");
|
||
return result;
|
||
} catch (e) {
|
||
print("❌ 隐写上传失败: $e");
|
||
rethrow;
|
||
}
|
||
}
|
||
} |