二次开发源码提交

This commit is contained in:
2026-05-15 08:52:48 +08:00
parent 4131f7321a
commit eb35a1d067
191 changed files with 34566 additions and 0 deletions
@@ -0,0 +1,220 @@
import 'package:flutter/foundation.dart';
import '../../data/models/admin_model.dart';
import '../../services/admin_service.dart';
import '../../core/utils/app_logger.dart';
enum AdminState { idle, loading, error }
/// 管理员数据 Provider
class AdminProvider extends ChangeNotifier {
AdminState _state = AdminState.idle;
List<AdminGroupModel> _groups = [];
List<AdminUserModel> _users = [];
PaginationModel? _groupsPagination;
PaginationModel? _usersPagination;
String? _errorMessage;
// 用户多选状态
final Set<int> _selectedUserIds = {};
bool _isSelectingUsers = false;
AdminState get state => _state;
List<AdminGroupModel> get groups => _groups;
List<AdminUserModel> get users => _users;
PaginationModel? get groupsPagination => _groupsPagination;
PaginationModel? get usersPagination => _usersPagination;
String? get errorMessage => _errorMessage;
bool get isLoading => _state == AdminState.loading;
Set<int> get selectedUserIds => _selectedUserIds;
bool get isSelectingUsers => _isSelectingUsers;
bool get hasSelectedUsers => _selectedUserIds.isNotEmpty;
final AdminService _service = AdminService.instance;
/// 加载用户组列表
Future<void> loadGroups({int page = 1}) async {
try {
final response = await _service.getGroups(page: page);
_groups = response.groups;
_groupsPagination = response.pagination;
notifyListeners();
} catch (e) {
AppLogger.d('加载用户组失败: $e');
_errorMessage = e.toString();
notifyListeners();
}
}
/// 加载用户列表
Future<void> loadUsers({int page = 1}) async {
try {
final response = await _service.getUsers(page: page);
_users = response.users;
_usersPagination = response.pagination;
notifyListeners();
} catch (e) {
AppLogger.d('加载用户列表失败: $e');
_errorMessage = e.toString();
notifyListeners();
}
}
/// 加载全部管理员数据
Future<void> loadAll() async {
_setState(AdminState.loading);
try {
await Future.wait([
loadGroups(),
loadUsers(),
]);
_setState(AdminState.idle);
} catch (e) {
_errorMessage = e.toString();
_setState(AdminState.error);
}
}
/// 创建用户组
Future<bool> createGroup(String name) async {
try {
final group = await _service.createGroup(name);
_groups.insert(0, group);
if (_groupsPagination != null) {
_groupsPagination = PaginationModel(
page: _groupsPagination!.page,
pageSize: _groupsPagination!.pageSize,
totalItems: _groupsPagination!.totalItems + 1,
);
}
notifyListeners();
return true;
} catch (e) {
AppLogger.d('创建用户组失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 删除用户组(会先检查组下是否有用户)
Future<String?> deleteGroup(int groupId) async {
try {
final detail = await _service.getGroupDetail(groupId);
if ((detail.totalUsers ?? 0) > 0) {
return '该组下有 ${detail.totalUsers} 个用户,请先删除或迁移用户';
}
await _service.deleteGroup(groupId);
_groups.removeWhere((g) => g.id == groupId);
if (_groupsPagination != null) {
_groupsPagination = PaginationModel(
page: _groupsPagination!.page,
pageSize: _groupsPagination!.pageSize,
totalItems: _groupsPagination!.totalItems - 1,
);
}
notifyListeners();
return null;
} catch (e) {
AppLogger.d('删除用户组失败: $e');
return e.toString();
}
}
/// 创建用户
Future<bool> createUser({
required String email,
required String nick,
required String password,
required int groupId,
}) async {
try {
final user = await _service.createUser(
email: email,
nick: nick,
password: password,
groupId: groupId,
);
_users.insert(0, user);
if (_usersPagination != null) {
_usersPagination = PaginationModel(
page: _usersPagination!.page,
pageSize: _usersPagination!.pageSize,
totalItems: _usersPagination!.totalItems + 1,
);
}
notifyListeners();
return true;
} catch (e) {
AppLogger.d('创建用户失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 批量删除用户
Future<bool> batchDeleteUsers(List<int> ids) async {
try {
await _service.batchDeleteUsers(ids);
_users.removeWhere((u) => ids.contains(u.id));
if (_usersPagination != null) {
_usersPagination = PaginationModel(
page: _usersPagination!.page,
pageSize: _usersPagination!.pageSize,
totalItems: _usersPagination!.totalItems - ids.length,
);
}
exitSelectMode();
notifyListeners();
return true;
} catch (e) {
AppLogger.d('批量删除用户失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
// --- 多选模式 ---
void toggleSelectMode() {
_isSelectingUsers = !_isSelectingUsers;
if (!_isSelectingUsers) {
_selectedUserIds.clear();
}
notifyListeners();
}
void exitSelectMode() {
_isSelectingUsers = false;
_selectedUserIds.clear();
notifyListeners();
}
void toggleUserSelection(int userId) {
if (_selectedUserIds.contains(userId)) {
_selectedUserIds.remove(userId);
} else {
_selectedUserIds.add(userId);
}
notifyListeners();
}
void selectAllUsers() {
_selectedUserIds.clear();
_selectedUserIds.addAll(_users.map((u) => u.id));
notifyListeners();
}
void clearUserSelection() {
_selectedUserIds.clear();
notifyListeners();
}
bool isUserSelected(int userId) => _selectedUserIds.contains(userId);
void _setState(AdminState state) {
_state = state;
notifyListeners();
}
}
@@ -0,0 +1,333 @@
import 'package:cloudreve4_flutter/presentation/widgets/toast_helper.dart';
import 'package:cloudreve4_flutter/presentation/widgets/user_avatar.dart';
import 'package:flutter/foundation.dart';
import '../../data/models/server_model.dart';
import '../../data/models/user_model.dart';
import '../../services/auth_service.dart';
import '../../services/server_service.dart';
import '../../services/storage_service.dart';
import '../../services/api_service.dart';
import '../../core/exceptions/app_exception.dart';
import '../../core/utils/app_logger.dart';
/// 认证状态
enum AuthState { loading, authenticated, unauthenticated, error }
/// 认证Provider
class AuthProvider extends ChangeNotifier {
AuthState _state = AuthState.unauthenticated;
UserModel? _user;
String? _errorMessage;
bool _hasRefreshTokenExpired = false;
AuthState get state => _state;
UserModel? get user => _user;
String? get errorMessage => _errorMessage;
bool get isAuthenticated => _state == AuthState.authenticated;
bool get isLoading => _state == AuthState.loading;
bool get hasRefreshTokenExpired => _hasRefreshTokenExpired;
bool get isAdmin {
final name = _user?.group?.name.toLowerCase();
return name == 'admin' || name == '管理员';
}
/// 当前选中的服务器
ServerModel? get currentServer => ServerService.instance.currentServer;
/// 获取当前用户的 token
TokenModel? get token => _user?.token;
/// 初始化
Future<void> init() async {
try {
setState(AuthState.loading);
// 初始化服务器服务
await ServerService.instance.init();
// 获取当前服务器
final server = ServerService.instance.currentServer;
if (server == null) {
setState(AuthState.unauthenticated);
return;
}
// 设置 API 的 baseUrl
await _setApiBaseUrl(server.baseUrl);
// 设置 ApiService 的认证回调
_setupApiCallbacks();
// 检查是否有保存的登录信息
if (server.user != null && server.user!.token != null) {
// 有保存的用户信息,检查 token 是否过期
if (!server.user!.token!.isRefreshTokenExpired) {
// Refresh token 未过期,设置用户信息
setUser(server.user);
setState(AuthState.authenticated);
return;
} else {
// Refresh token 已过期,清除登录信息
await ServerService.instance.clearCurrentServerLogin();
}
}
_user = null;
setState(AuthState.unauthenticated);
} catch (e) {
AppLogger.d('AuthProvider 初始化失败: $e');
_user = null;
setState(AuthState.unauthenticated);
}
}
/// 设置 ApiService 的认证回调
void _setupApiCallbacks() {
ApiService.setAuthCallbacks(
getToken: () async {
// 返回当前的 access token
return _user?.token?.accessToken;
},
refreshToken: () async {
// 刷新 token
try {
await refreshToken();
} catch (e) {
// 刷新失败,设置过期标志
if (e is RefreshTokenExpiredException) {
setRefreshTokenExpired();
}
rethrow;
}
},
clearAuth: () async {
// 清除认证数据
await ServerService.instance.clearCurrentServerLogin();
setRefreshTokenExpired();
},
);
}
/// 设置 API baseUrl
Future<void> _setApiBaseUrl(String baseUrl) async {
// 同时更新存储和 ApiService 的 baseUrl
final storageService = StorageService.instance;
await storageService.setCustomBaseUrl(baseUrl);
await ApiService.instance.setBaseUrl(baseUrl);
}
/// 密码登录
Future<bool> passwordLogin({
required String email,
required String password,
bool rememberMe = false,
}) async {
try {
setState(AuthState.loading);
// 获取当前服务器
final server = ServerService.instance.currentServer;
if (server == null) {
_errorMessage = '服务器初始化失败';
_user = null;
setState(AuthState.error);
return false;
}
// 每次登录时都重新设置 API 的 baseUrl,确保使用最新的服务器地址
await _setApiBaseUrl(server.baseUrl);
// 执行登录
final response = await AuthService.instance.passwordLogin(
email: email,
password: password,
);
AppLogger.d('AuthProvider 登录成功: $response');
// 保存登录信息到当前服务器(包含完整 user 和 token
await ServerService.instance.updateCurrentServerLogin(
email: rememberMe ? email : null,
password: rememberMe ? password : null,
user: response.user,
rememberMe: rememberMe,
);
setUser(response.user);
setState(AuthState.authenticated);
return true;
} on TwoFactorRequiredException {
// 两步验证需要,不设置 error 状态,重新抛出让调用方处理
setState(AuthState.unauthenticated);
rethrow;
} catch (e) {
_errorMessage = e.toString();
_user = null;
setState(AuthState.error);
return false;
}
}
/// 两步验证登录
Future<bool> twoFactorLogin({
required String otp,
required String sessionId,
required String email,
required String password,
bool rememberMe = false,
}) async {
try {
setState(AuthState.loading);
final response = await AuthService.instance.twoFactorLogin(
otp: otp,
sessionId: sessionId,
);
await ServerService.instance.updateCurrentServerLogin(
email: rememberMe ? email : null,
password: rememberMe ? password : null,
user: response.user,
rememberMe: rememberMe,
);
setUser(response.user);
setState(AuthState.authenticated);
return true;
} catch (e) {
_errorMessage = e.toString();
_user = null;
setState(AuthState.error);
return false;
}
}
/// 登出
Future<void> logout() async {
try {
// 调用登出 API(需要 token
if (token?.refreshToken != null) {
try {
await AuthService.instance.logout();
} catch (e) {
// 登出 API 调用失败不影响本地清理
AppLogger.d('登出 API 调用失败: $e');
}
}
// 清除当前服务器的登录信息
await ServerService.instance.clearCurrentServerLogin();
_clearUserData();
// 清除头像缓存
await UserAvatar.clearAllCache();
setState(AuthState.unauthenticated);
} catch (e) {
// 即使出错也要清除本地状态
_clearUserData();
setState(AuthState.unauthenticated);
_errorMessage = e.toString();
}
}
/// 刷新用户信息
Future<void> refreshUser() async {
try {
final user = await AuthService.instance.getCurrentUser();
setUser(user);
// 更新服务器中的用户信息(保留 token)
final server = ServerService.instance.currentServer;
if (server != null && token != null) {
await ServerService.instance.updateCurrentServerLogin(
user: user.copyWith(token: token),
);
}
} catch (e) {
_errorMessage = e.toString();
}
}
/// 刷新 token
Future<void> refreshToken() async {
try {
final currentToken = token;
if (currentToken == null || currentToken.isRefreshTokenExpired) {
ToastHelper.failure('已注销或Refresh token 已过期,需要重新登录');
throw Exception('Refresh token 已过期,需要重新登录');
}
// 调用刷新 token 接口
final newToken = await AuthService.instance.refreshToken(currentToken.refreshToken);
// 更新用户信息中的 token
final updatedUser = _user!.copyWith(token: newToken);
// 保存到服务器
await ServerService.instance.updateCurrentServerLogin(user: updatedUser);
setUser(updatedUser);
} catch (e) {
ToastHelper.error('刷新 token 失败: $e');
AppLogger.d('刷新 token 失败: $e');
_user = null;
_errorMessage = e.toString();
notifyListeners();
rethrow;
}
}
/// 设置用户
void setUser(UserModel? user) {
_user = user;
notifyListeners();
}
/// 设置状态
void setState(AuthState state) {
_state = state;
notifyListeners();
}
/// 清除错误
void clearError() {
_errorMessage = null;
notifyListeners();
}
/// 清除用户数据
void _clearUserData() {
_user = null;
_errorMessage = null;
_hasRefreshTokenExpired = false;
notifyListeners();
}
/// 处理 RefreshTokenExpiredException
void setRefreshTokenExpired() {
_hasRefreshTokenExpired = true;
notifyListeners();
}
void clearRefreshTokenExpired() {
_hasRefreshTokenExpired = false;
notifyListeners();
}
/// 获取上次登录的邮箱(用于填充输入框)
String? get rememberedEmail {
final server = ServerService.instance.currentServer;
return server?.email;
}
/// 获取上次登录的密码(用于填充输入框)
String? get rememberedPassword {
final server = ServerService.instance.currentServer;
return server?.password;
}
/// 是否记住密码
bool get rememberMe {
final server = ServerService.instance.currentServer;
return server?.rememberMe ?? false;
}
}
@@ -0,0 +1,447 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../../core/constants/storage_keys.dart';
import '../../data/models/download_task_model.dart';
import '../../services/download_service.dart';
import '../../services/storage_service.dart';
import '../../core/utils/app_logger.dart';
/// 下载管理Provider
class DownloadManagerProvider extends ChangeNotifier {
final DownloadService _downloadService = DownloadService();
final Map<String, DownloadTaskModel> _tasks = {};
bool _isInitialized = false;
bool _isWifiOnlyEnabled = false;
// 速度追踪:记录每个任务的上次进度更新时间和字节数
final Map<String, DateTime> _lastProgressTime = {};
final Map<String, int> _lastProgressBytes = {};
/// 获取所有下载任务
List<DownloadTaskModel> get tasks => _tasks.values.toList()
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
/// 获取指定状态的任务
List<DownloadTaskModel> getTasksByStatus(DownloadStatus status) {
return tasks.where((task) => task.status == status).toList();
}
/// 下载中的任务数
int get downloadingCount =>
getTasksByStatus(DownloadStatus.downloading).length;
/// 活跃任务数(下载中 + 等待中 + 暂停)
int get activeTaskCount => tasks
.where((t) =>
t.status == DownloadStatus.downloading ||
t.status == DownloadStatus.waiting ||
t.status == DownloadStatus.paused)
.length;
/// WiFi-only 设置
bool get isWifiOnlyEnabled => _isWifiOnlyEnabled;
/// 初始化下载服务
Future<void> initialize() async {
if (_isInitialized) return;
await _downloadService.initialize(callbackHandler: _handleDownloadCallback);
// 加载 WiFi-only 设置
_isWifiOnlyEnabled =
await StorageService.instance.getBool(StorageKeys.downloadWifiOnly) ??
false;
// 从本地存储加载已保存的下载任务
await _loadTasks();
_isInitialized = true;
AppLogger.d('DownloadManagerProvider 初始化完成');
}
/// 更新 WiFi-only 设置,并同步等待中的任务
Future<void> setWifiOnlyEnabled(bool value) async {
_isWifiOnlyEnabled = value;
await StorageService.instance
.setBool(StorageKeys.downloadWifiOnly, value);
// 如果关闭了WiFi-only,需要将等待WiFi的任务重新入队
if (!value) {
for (final task in _tasks.values.toList()) {
if (task.waitingForWifi) {
// 取消当前等待WiFi的任务,重新入队(不需要WiFi)
await _downloadService.cancelDownload(task.id);
_tasks[task.id] = task.copyWith(
status: DownloadStatus.waiting,
waitingForWifi: false,
);
await _saveTasks();
// 重新开始下载
await _downloadService.startDownload(_tasks[task.id]!);
}
}
}
notifyListeners();
}
/// 添加下载任务
Future<DownloadTaskModel?> addDownloadTask({
required String fileName,
required String fileUri,
required int fileSize,
String? savePath,
}) async {
// 如果已存在相同文件的任务,返回null
DownloadTaskModel? existingTask;
for (final task in _tasks.values) {
if (task.fileUri == fileUri) {
existingTask = task;
break;
}
}
if (existingTask != null) {
return null;
}
// 确保下载服务已初始化
await initialize();
// 获取保存路径
if (savePath == null) {
final dir = await _downloadService.getDownloadDirectory();
savePath = '${dir.path}/$fileName';
}
// 创建任务ID
final id = DateTime.now().millisecondsSinceEpoch.toString();
final task = DownloadTaskModel(
id: id,
fileName: fileName,
fileUri: fileUri,
fileSize: fileSize,
savePath: savePath,
status: DownloadStatus.waiting,
);
_tasks[id] = task;
await _saveTasks();
notifyListeners();
// 开始下载
AppLogger.d(
'准备开始下载任务: ${task.id}, 文件: ${task.fileName}, 下载状态: ${task.status}');
final bdTaskId = await _downloadService.startDownload(task);
AppLogger.d('startDownload 返回: bdTaskId=$bdTaskId');
if (bdTaskId == null) {
// 下载失败,更新任务状态
_tasks[id] = task.copyWith(
status: DownloadStatus.failed,
errorMessage: '无法创建下载任务',
);
notifyListeners();
return null;
}
return task;
}
/// 批量添加下载任务
Future<void> addBatchDownloadTasks(List<Map<String, dynamic>> files) async {
await initialize();
final dir = await _downloadService.getDownloadDirectory();
for (final file in files) {
final fileName = file['name'] as String;
final fileUri = file['path'] as String;
final fileSize = file['size'] as int? ?? 0;
await addDownloadTask(
fileName: fileName,
fileUri: fileUri,
fileSize: fileSize,
savePath: '${dir.path}/$fileName',
);
}
}
/// 处理下载回调
void _handleDownloadCallback(
String taskId, DownloadStatus status, int progress) async {
AppLogger.d(
'DownloadManagerProvider._handleDownloadCallback: taskId=$taskId, status=$status, progress=$progress');
// 获取当前任务
final task = _tasks[taskId];
if (task == null) {
AppLogger.d('任务不存在: taskId=$taskId');
return;
}
// 计算下载字节数和速度
final downloadedBytes = (task.fileSize * progress / 100).toInt();
int speed = 0;
final now = DateTime.now();
final lastTime = _lastProgressTime[taskId];
final lastBytes = _lastProgressBytes[taskId] ?? 0;
if (status == DownloadStatus.downloading && lastTime != null) {
final elapsed = now.difference(lastTime).inMilliseconds;
if (elapsed > 0) {
speed = ((downloadedBytes - lastBytes) * 1000 / elapsed).round();
}
}
if (status == DownloadStatus.downloading) {
_lastProgressTime[taskId] = now;
_lastProgressBytes[taskId] = downloadedBytes;
} else {
_lastProgressTime.remove(taskId);
_lastProgressBytes.remove(taskId);
}
// 判断是否在等待WiFi
final waitingForWifi =
status == DownloadStatus.waiting && _isWifiOnlyEnabled;
// 更新任务
final updatedTask = task.copyWith(
status: status,
downloadedBytes: downloadedBytes,
speed: speed,
waitingForWifi: waitingForWifi,
);
// 如果下载完成,设置完成时间
if (status == DownloadStatus.completed) {
_tasks[taskId] = updatedTask.copyWith(
completedAt: DateTime.now(),
speed: 0,
);
} else {
_tasks[taskId] = updatedTask;
}
AppLogger.d('任务已更新: ${_tasks[taskId]!.status}');
await _saveTasks();
notifyListeners();
}
/// 恢复下载
Future<void> resumeDownload(String taskId) async {
final task = _tasks[taskId];
if (task != null) {
await _downloadService.resumeDownload(taskId);
}
}
/// 暂停下载
Future<void> pauseDownload(String taskId) async {
await _downloadService.pauseDownload(taskId);
final task = _tasks[taskId];
if (task != null) {
if (task.status == DownloadStatus.downloading) {
_tasks[taskId] = task.copyWith(
status: DownloadStatus.paused, speed: 0, waitingForWifi: false);
_lastProgressTime.remove(taskId);
_lastProgressBytes.remove(taskId);
await _saveTasks();
notifyListeners();
}
}
}
/// 取消下载
Future<void> cancelDownload(String taskId) async {
await _downloadService.cancelDownload(taskId);
final task = _tasks[taskId];
if (task != null) {
_tasks[taskId] = task.copyWith(
status: DownloadStatus.cancelled, waitingForWifi: false);
await _saveTasks();
notifyListeners();
// 延迟移除任务,同时从存储中删除
Future.delayed(const Duration(seconds: 2), () {
_tasks.remove(taskId);
_downloadService.disposeTask(taskId);
_saveTasks();
notifyListeners();
});
}
}
/// 删除下载任务(包括文件)
Future<void> deleteDownloadTask(String taskId) async {
final task = _tasks[taskId];
if (task != null) {
// 删除已下载的文件
if (task.status == DownloadStatus.completed) {
await _downloadService.deleteDownloadedFile(task.savePath);
}
// 移除任务
_tasks.remove(taskId);
_downloadService.disposeTask(taskId);
await _saveTasks();
notifyListeners();
}
}
/// 重新下载
Future<void> retryDownload(String taskId) async {
final task = _tasks[taskId];
if (task != null) {
// 删除已下载的部分文件
await _downloadService.deleteDownloadedFile(task.savePath);
// 重置任务状态
_tasks[taskId] = task.copyWith(
downloadedBytes: 0,
speed: 0,
status: DownloadStatus.waiting,
errorMessage: null,
completedAt: null,
waitingForWifi: false,
);
_lastProgressTime.remove(taskId);
_lastProgressBytes.remove(taskId);
await _saveTasks();
notifyListeners();
// 重新开始下载
await _downloadService.startDownload(_tasks[taskId]!);
}
}
/// 清空所有已完成的任务
Future<void> clearCompletedTasks() async {
final completedTasks = getTasksByStatus(DownloadStatus.completed);
for (final task in completedTasks) {
await deleteDownloadTask(task.id);
}
}
/// 清空所有失败的任务
Future<void> clearFailedTasks() async {
final failedTasks = getTasksByStatus(DownloadStatus.failed);
for (final task in failedTasks) {
_tasks.remove(task.id);
_downloadService.disposeTask(task.id);
}
await _saveTasks();
notifyListeners();
}
/// 获取任务
DownloadTaskModel? getTask(String taskId) {
return _tasks[taskId];
}
/// 从本地存储加载下载任务
Future<void> _loadTasks() async {
try {
final tasksJson =
await StorageService.instance.getString(StorageKeys.downloadTasks);
if (tasksJson == null || tasksJson.isEmpty) {
AppLogger.d('没有保存的下载任务');
return;
}
final tasksList = jsonDecode(tasksJson) as List<dynamic>;
final loadedTasks = <DownloadTaskModel>[];
final now = DateTime.now();
for (final taskJson in tasksList) {
try {
final task =
DownloadTaskModel.fromJson(taskJson as Map<String, dynamic>);
// 过滤掉已取消的任务(修复4:已取消任务不恢复)
if (task.status == DownloadStatus.cancelled) {
continue;
}
// 如果任务已完成,只保留配置天数内的记录
if (task.status == DownloadStatus.completed) {
if (task.completedAt == null) {
continue;
}
final retentionDays = await StorageService.instance
.getInt(StorageKeys.taskRetentionDays) ??
7;
// retentionDays == -1 表示永不过期
if (retentionDays > 0) {
final daysSinceCompletion =
now.difference(task.completedAt!).inDays;
if (daysSinceCompletion > retentionDays) {
AppLogger.d(
'跳过超过$retentionDays天的已完成任务: ${task.fileName}');
continue;
}
}
}
loadedTasks.add(task);
} catch (e) {
AppLogger.d('解析下载任务失败: $e');
}
}
// 将加载的任务添加到当前任务列表
for (final task in loadedTasks) {
_tasks[task.id] = task;
}
AppLogger.d('从存储加载了 ${loadedTasks.length} 个下载任务');
// 通知 UI 更新
if (loadedTasks.isNotEmpty) {
notifyListeners();
}
// 恢复未完成的任务
for (final task in loadedTasks) {
if (task.status == DownloadStatus.downloading ||
task.status == DownloadStatus.waiting) {
AppLogger.d('恢复下载任务: ${task.fileName}');
// 使用 resumeDownloadAfterRestart 支持断点续传
await _downloadService.resumeDownloadAfterRestart(task);
} else if (task.status == DownloadStatus.paused) {
// 修复5:暂停的任务需要重建 bdTasks 映射,以便继续下载
AppLogger.d('重建暂停任务映射: ${task.fileName}');
await _downloadService.resumeDownloadAfterRestart(task);
// 重建映射后立即暂停,保持任务在暂停状态
await _downloadService.pauseDownload(task.id);
}
}
} catch (e) {
AppLogger.d('加载下载任务失败: $e');
}
}
/// 保存下载任务到本地存储
Future<void> _saveTasks() async {
try {
final tasksList = _tasks.values.map((task) => task.toJson()).toList();
final tasksJson = jsonEncode(tasksList);
await StorageService.instance
.setString(StorageKeys.downloadTasks, tasksJson);
AppLogger.d('已保存 ${_tasks.length} 个下载任务到存储');
} catch (e) {
AppLogger.d('保存下载任务失败: $e');
}
}
@override
void dispose() {
_downloadService.dispose();
super.dispose();
}
}
@@ -0,0 +1,410 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import '../../data/models/file_model.dart';
import '../../services/file_service.dart';
import '../../services/thumbnail_service.dart';
import '../../core/utils/app_logger.dart';
import '../../core/utils/file_utils.dart';
/// 文件视图类型
enum FileViewType { list, grid, gallery }
/// 刷新结果
class RefreshResult {
final int added;
final int removed;
final int updated;
const RefreshResult({required this.added, required this.removed, required this.updated});
bool get isUnchanged => added == 0 && removed == 0 && updated == 0;
}
/// 文件管理Provider
class FileManagerProvider extends ChangeNotifier {
String _currentPath = '/';
List<FileModel> _files = [];
List<String> _selectedFiles = [];
FileViewType _viewType = FileViewType.list;
bool _isLoading = false;
bool _hasMore = true;
String? _errorMessage;
String? _contextHint;
String? _highlightPath;
Timer? _highlightTimer;
String get currentPath => _currentPath;
List<FileModel> get files => _files;
List<String> get selectedFiles => _selectedFiles;
FileViewType get viewType => _viewType;
bool get isLoading => _isLoading;
bool get hasMore => _hasMore;
String? get errorMessage => _errorMessage;
String? get contextHint => _contextHint;
bool get hasSelection => _selectedFiles.isNotEmpty;
String? get highlightPath => _highlightPath;
/// 加载文件列表
Future<void> loadFiles({bool refresh = false, Duration timeout = const Duration(seconds: 5)}) async {
if (refresh) {
_selectedFiles.clear();
}
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final response = await FileService().listFiles(
uri: _currentPath,
pageSize: 50,
).timeout(timeout);
final List<dynamic> filesData = response['files'] as List<dynamic>? ?? [];
final pagination = response['pagination'] as Map<String, dynamic>? ?? {};
AppLogger.d("获取files列表: $filesData");
setState(() {
_files = filesData
.map((f) => FileModel.fromJson(f as Map<String, dynamic>))
.toList();
_hasMore = pagination['next_token'] != null;
_contextHint = response['context_hint'] as String?;
});
} on TimeoutException {
setState(() {
_errorMessage = '加载超时,请检查网络后重试';
_hasMore = false;
});
} catch (e) {
setState(() {
_errorMessage = e.toString();
_hasMore = false;
});
} finally {
setState(() {
_isLoading = false;
});
}
}
/// 进入文件夹
Future<void> enterFolder(String path) async {
_currentPath = path;
_selectedFiles.clear();
_highlightPath = null;
_highlightTimer?.cancel();
ThumbnailService.instance.clearAll();
await loadFiles();
}
/// 返回上级
Future<void> goBack() async {
if (_currentPath == '/' || _currentPath.isEmpty) return;
final parts = _currentPath.split('/');
if (parts.length > 1) {
parts.removeLast();
_currentPath = parts.join('/');
} else {
_currentPath = '/';
}
_selectedFiles.clear();
_highlightPath = null;
_highlightTimer?.cancel();
ThumbnailService.instance.clearAll();
notifyListeners();
await loadFiles();
}
/// 选择/取消选择文件
void toggleSelection(String path) {
if (_selectedFiles.contains(path)) {
_selectedFiles.remove(path);
} else {
_selectedFiles.add(path);
}
notifyListeners();
}
/// 选择所有
void selectAll() {
_selectedFiles = _files.map((f) => f.path).toList();
notifyListeners();
}
/// 清除选择
void clearSelection() {
_selectedFiles.clear();
notifyListeners();
}
/// 切换视图类型
void setViewType(FileViewType type) {
_viewType = type;
notifyListeners();
}
/// 设置错误信息
void setErrorMessage(String? message) {
_errorMessage = message;
notifyListeners();
}
/// 设置状态
void setState(VoidCallback fn) {
fn();
notifyListeners();
}
/// 删除选中的文件
Future<String?> deleteSelectedFiles() async {
if (_selectedFiles.isEmpty) return null;
try {
AppLogger.d("删除文件: ${_selectedFiles.join(', ')}");
await FileService().deleteFiles(uris: _selectedFiles);
setState(() {
_files.removeWhere((file) => _selectedFiles.contains(file.path));
});
clearSelection();
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 创建文件夹
Future<String?> createFolder(String name) async {
try {
String uri;
if (_currentPath == '/' || _currentPath.isEmpty) {
uri = '/$name';
} else {
uri = '$_currentPath/$name';
}
final response = await FileService().createFile(
uri: uri,
type: 'folder',
errOnConflict: true,
);
final newFolder = FileModel.fromJson(response);
setState(() {
_files.insert(0, newFolder);
});
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 删除单个文件(增量移除)
Future<String?> deleteFile(String path) async {
try {
await FileService().deleteFiles(uris: [path]);
setState(() {
_files.removeWhere((file) => file.path == path);
_selectedFiles.remove(path);
});
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 移动文件(增量更新)
Future<String?> moveFiles(List<String> uris, String destination, {bool copy = false}) async {
try {
await FileService().moveFiles(uris: uris, dst: destination);
clearSelection();
if (!copy) {
// 移动:文件离开当前目录,直接从列表移除
setState(() {
_files.removeWhere((file) => uris.contains(file.path));
});
} else {
// 复制:仅当目标是当前目录时需要刷新
final normalizedDst = FileUtils.toCloudreveUri(destination);
final normalizedCur = FileUtils.toCloudreveUri(_currentPath);
if (normalizedDst == normalizedCur) {
await loadFiles();
}
}
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 重命名文件(原地更新,不刷新列表)
Future<String?> renameFile(String path, String newName) async {
try {
final response = await FileService().renameFile(uri: path, newName: newName);
if (response.isEmpty) {
await loadFiles();
return null;
}
final updatedFile = FileModel.fromJson(response);
final index = _files.indexWhere((f) => f.path == path);
if (index != -1) {
setState(() {
_files[index] = updatedFile;
});
}
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 通过 URI 获取文件信息并添加到列表(用于上传完成后)
Future<void> addFileByUri(String fileUri) async {
try {
final response = await FileService().getFileInfo(uri: fileUri);
final newFile = FileModel.fromJson(response);
final exists = _files.any((f) => f.id == newFile.id);
if (!exists) {
setState(() {
_files.insert(0, newFile);
});
}
} catch (e) {
AppLogger.d('获取上传文件信息失败: $e');
}
}
/// 高亮指定文件路径(3 秒后自动清除)
void setHighlightPath(String? path) {
_highlightTimer?.cancel();
_highlightPath = path;
notifyListeners();
if (path != null) {
_highlightTimer = Timer(const Duration(seconds: 3), () {
_highlightPath = null;
notifyListeners();
});
}
}
/// 导航到指定文件夹并高亮目标文件
Future<void> navigateAndHighlight(String folderPath, String filePath) async {
_currentPath = folderPath;
_selectedFiles.clear();
_highlightPath = null;
_highlightTimer?.cancel();
await loadFiles();
setHighlightPath(filePath);
}
/// 清空文件列表
void clearFiles() {
setState(() {
_files = [];
_selectedFiles = [];
_currentPath = '/';
_errorMessage = null;
});
}
/// 智能刷新 - 只更新差异部分
Future<RefreshResult> refreshFiles({Duration timeout = const Duration(seconds: 5)}) async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final response = await FileService().listFiles(
uri: _currentPath,
pageSize: 50,
).timeout(timeout);
final List<dynamic> filesData = response['files'] as List<dynamic>? ?? [];
final newFiles = filesData
.map((f) => FileModel.fromJson(f as Map<String, dynamic>))
.toList();
final currentMap = <String, FileModel>{};
for (final file in _files) {
currentMap[file.path] = file;
}
final newMap = <String, FileModel>{};
for (final file in newFiles) {
newMap[file.path] = file;
}
int added = 0;
int removed = 0;
int updated = 0;
final updatedFiles = <FileModel>[];
for (final file in newFiles) {
final existingFile = currentMap[file.path];
if (existingFile != null) {
if (existingFile.updatedAt != file.updatedAt ||
existingFile.size != file.size) {
updatedFiles.add(file);
updated++;
} else {
updatedFiles.add(existingFile);
}
} else {
updatedFiles.add(file);
added++;
}
}
for (final file in _files) {
if (!newMap.containsKey(file.path)) {
removed++;
}
}
setState(() {
_files = updatedFiles;
_hasMore = response['pagination']?['next_token'] != null;
_contextHint = response['context_hint'] as String?;
});
return RefreshResult(added: added, removed: removed, updated: updated);
} on TimeoutException {
setState(() {
_errorMessage = '加载超时,请检查网络后重试';
});
return const RefreshResult(added: 0, removed: 0, updated: 0);
} catch (e) {
setState(() {
_errorMessage = e.toString();
});
return const RefreshResult(added: 0, removed: 0, updated: 0);
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
void dispose() {
_highlightTimer?.cancel();
super.dispose();
}
}
@@ -0,0 +1,14 @@
import 'package:flutter/foundation.dart';
class NavigationProvider extends ChangeNotifier {
int _currentIndex = 0; // 默认概览页
int get currentIndex => _currentIndex;
void setIndex(int index) {
if (_currentIndex != index) {
_currentIndex = index;
notifyListeners();
}
}
}
@@ -0,0 +1,249 @@
import 'dart:io';
import 'package:flutter/material.dart';
import '../../services/storage_service.dart';
/// 主题模式
enum AppThemeMode {
light,
dark,
system,
}
/// 主题Provider - 管理主题模式和主题色
class ThemeProvider extends ChangeNotifier {
AppThemeMode _themeMode = AppThemeMode.system;
Color _seedColor = const Color(0xFF3B82F6);
static const Color lightScaffoldBg = Color(0xFFF8FAFC);
static const Color darkScaffoldBg = Color(0xFF0F172A);
AppThemeMode get themeMode => _themeMode;
Color get seedColor => _seedColor;
bool get isDark => _themeMode == AppThemeMode.dark;
/// 初始化
Future<void> init() async {
await Future.wait([
loadThemeMode(),
loadSeedColor(),
]);
}
/// 加载主题模式
Future<void> loadThemeMode() async {
final savedMode = await StorageService.instance.themeMode;
if (savedMode != null) {
switch (savedMode) {
case 'light':
_themeMode = AppThemeMode.light;
case 'dark':
_themeMode = AppThemeMode.dark;
default:
_themeMode = AppThemeMode.system;
}
}
notifyListeners();
}
/// 加载主题色
Future<void> loadSeedColor() async {
final saved = await StorageService.instance.getString('theme_seed_color');
if (saved != null && saved.isNotEmpty) {
final color = _colorFromHex(saved);
if (color != null) {
_seedColor = color;
notifyListeners();
}
}
}
/// 设置主题模式
Future<void> setThemeMode(AppThemeMode mode) async {
_themeMode = mode;
notifyListeners();
String modeString;
switch (mode) {
case AppThemeMode.light:
modeString = 'light';
case AppThemeMode.dark:
modeString = 'dark';
case AppThemeMode.system:
modeString = 'system';
}
await StorageService.instance.setThemeMode(modeString);
}
/// 设置主题色
Future<void> setSeedColor(Color color) async {
_seedColor = color;
notifyListeners();
await StorageService.instance.setString('theme_seed_color', _colorToHex(color));
}
/// 切换主题
Future<void> toggleTheme() async {
final newMode = isDark ? AppThemeMode.light : AppThemeMode.dark;
await setThemeMode(newMode);
}
/// 构建亮色主题
ThemeData buildLightTheme() {
return _buildTheme(Brightness.light);
}
/// 构建暗色主题
ThemeData buildDarkTheme() {
return _buildTheme(Brightness.dark);
}
ThemeData _buildTheme(Brightness brightness) {
final isLight = brightness == Brightness.light;
final colorScheme = ColorScheme.fromSeed(
seedColor: _seedColor,
brightness: brightness,
);
final bodyColor = isLight ? Colors.black87 : Colors.white;
final displayColor = isLight ? Colors.black87 : Colors.white;
final baseTextTheme = ThemeData(brightness: brightness).textTheme;
var textTheme = baseTextTheme.apply(
bodyColor: bodyColor,
displayColor: displayColor,
fontFamily: _getPlatformFont(),
);
if (_getPlatformFont() == 'NotoSansSC') {
textTheme = textTheme.copyWith(
bodyLarge: textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
bodyMedium: textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500),
bodySmall: textTheme.bodySmall?.copyWith(fontWeight: FontWeight.w500),
titleLarge: textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
titleMedium: textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
titleSmall: textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
labelLarge: textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w700),
);
}
return ThemeData(
textTheme: textTheme,
useMaterial3: true,
colorScheme: colorScheme,
scaffoldBackgroundColor: isLight ? lightScaffoldBg : darkScaffoldBg,
appBarTheme: AppBarTheme(
centerTitle: true,
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor: isLight
? lightScaffoldBg.withValues(alpha: 0.85)
: darkScaffoldBg.withValues(alpha: 0.85),
surfaceTintColor: Colors.transparent,
foregroundColor: isLight ? Colors.black87 : Colors.white,
),
cardTheme: CardThemeData(
elevation: 0,
shadowColor: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: isLight
? Colors.black.withValues(alpha: 0.06)
: Colors.white.withValues(alpha: 0.08),
),
),
color: isLight ? Colors.white : const Color(0xFF1E293B),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
chipTheme: ChipThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
dialogTheme: DialogThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
bottomSheetTheme: const BottomSheetThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(16),
),
),
),
navigationBarTheme: NavigationBarThemeData(
elevation: 0,
backgroundColor: isLight
? lightScaffoldBg.withValues(alpha: 0.9)
: darkScaffoldBg.withValues(alpha: 0.9),
indicatorColor: colorScheme.primary.withValues(alpha: 0.12),
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
),
navigationRailTheme: NavigationRailThemeData(
elevation: 0,
backgroundColor: isLight
? lightScaffoldBg
: darkScaffoldBg,
indicatorColor: colorScheme.primary.withValues(alpha: 0.12),
),
);
}
/// Color → hex string (不含alpha)
static String _colorToHex(Color color) {
return '#${color.toARGB32().toRadixString(16).padLeft(8, '0').substring(2)}';
}
/// hex string → Color
static Color? _colorFromHex(String hex) {
final clean = hex.replaceFirst('#', '');
if (clean.length == 6) {
return Color(int.parse('FF$clean', radix: 16));
}
if (clean.length == 8) {
return Color(int.parse(clean, radix: 16));
}
return null;
}
String? _getPlatformFont() {
if (Platform.isWindows || Platform.isLinux) return 'NotoSansSC';
if (Platform.isMacOS) return 'PingFang SC';
return null;
}
}
@@ -0,0 +1,103 @@
import 'dart:io';
import 'package:flutter/material.dart';
import '../../data/models/upload_task_model.dart';
import '../../services/upload_service.dart';
/// 上传管理Provider
class UploadManagerProvider extends ChangeNotifier {
final UploadService _uploadService = UploadService.instance;
bool _isInitialized = false;
bool _shouldShowDialog = false;
bool get showUploadDialog => _shouldShowDialog && _uploadService.allTasks.isNotEmpty;
List<UploadTaskModel> get allTasks => _uploadService.allTasks;
List<UploadTaskModel> get activeTasks => _uploadService.activeTasks;
/// 初始化上传管理器
Future<void> initialize() async {
if (_isInitialized) return;
await _uploadService.initialize();
_uploadService.addListener(_onServiceChanged);
_isInitialized = true;
}
void _onServiceChanged() {
notifyListeners();
}
/// 标记应该显示对话框
void markShouldShowDialog() {
_shouldShowDialog = true;
notifyListeners();
}
/// 隐藏对话框
void hideDialog() {
_shouldShowDialog = false;
notifyListeners();
}
/// 开始上传
Future<void> startUpload(
List<File> files,
String targetPath,
) async {
for (final file in files) {
// 构建目标路径 URI
String uri;
if (targetPath.startsWith('cloudreve://my')) {
uri = targetPath;
} else {
// 移除前导斜杠避免重复
String pathPart = targetPath;
if (pathPart.startsWith('/')) {
pathPart = pathPart.substring(1);
}
uri = pathPart.isEmpty ? 'cloudreve://my' : 'cloudreve://my/$pathPart';
}
final task = UploadTaskModel(
id: DateTime.now().millisecondsSinceEpoch.toString() + file.path,
file: file,
fileName: file.uri.pathSegments.last,
fileSize: await file.length(),
targetPath: uri,
);
_uploadService.addTask(task);
// 开始上传任务
_uploadService.startUpload(task);
}
}
/// 取消上传
void cancelUpload(String taskId) {
_uploadService.cancelUpload(taskId);
}
/// 重试上传
void retryUpload(String taskId) {
_uploadService.retryUpload(taskId);
}
/// 删除任务
void removeTask(String taskId) {
_uploadService.removeTask(taskId);
}
/// 清除所有已完成的任务
void clearCompletedTasks() {
_uploadService.clearCompletedTasks();
}
/// 清除失败的任务
void clearFailedTasks() {
_uploadService.clearFailedTasks();
}
@override
void dispose() {
_uploadService.removeListener(_onServiceChanged);
super.dispose();
}
}
@@ -0,0 +1,251 @@
import 'package:flutter/foundation.dart';
import '../../data/models/user_setting_model.dart';
import '../../services/user_setting_service.dart';
import '../../core/utils/app_logger.dart';
/// 用户设置状态
enum UserSettingState { idle, loading, error }
/// 用户设置 Provider
class UserSettingProvider extends ChangeNotifier {
UserSettingState _state = UserSettingState.idle;
UserSettingModel? _settings;
UserCapacityModel? _capacity;
String? _errorMessage;
UserSettingState get state => _state;
UserSettingModel? get settings => _settings;
UserCapacityModel? get capacity => _capacity;
String? get errorMessage => _errorMessage;
bool get isLoading => _state == UserSettingState.loading;
final UserSettingService _service = UserSettingService.instance;
/// 加载用户设置
Future<void> loadSettings() async {
try {
_setState(UserSettingState.loading);
_settings = await _service.getUserSetting();
_setState(UserSettingState.idle);
} catch (e) {
AppLogger.d('加载用户设置失败: $e');
_errorMessage = e.toString();
_setState(UserSettingState.error);
}
}
/// 加载存储用量
Future<void> loadCapacity() async {
try {
_capacity = await _service.getUserCapacity();
notifyListeners();
} catch (e) {
AppLogger.d('加载存储用量失败: $e');
}
}
/// 同时加载设置和容量
Future<void> loadAll() async {
await Future.wait([
loadSettings(),
loadCapacity(),
]);
}
/// 修改昵称
Future<bool> updateNick(String nick) async {
try {
await _service.updateNick(nick);
// 成功后刷新设置
await loadSettings();
return true;
} catch (e) {
AppLogger.d('修改昵称失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 修改主题色
Future<bool> updatePreferredTheme(String themeColor) async {
try {
await _service.updatePreferredTheme(themeColor);
if (_settings != null) {
_settings = _settings!.copyWith(); // 本地无需维护此字段
}
return true;
} catch (e) {
AppLogger.d('修改主题色失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 修改语言
Future<bool> updateLanguage(String language) async {
try {
await _service.updateLanguage(language);
return true;
} catch (e) {
AppLogger.d('修改语言失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 修改密码
Future<bool> changePassword({
required String currentPassword,
required String newPassword,
}) async {
try {
await _service.changePassword(
currentPassword: currentPassword,
newPassword: newPassword,
);
return true;
} catch (e) {
AppLogger.d('修改密码失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 启用2FA
Future<String?> prepare2FA() async {
try {
return await _service.prepare2FA();
} catch (e) {
AppLogger.d('准备启用2FA失败: $e');
_errorMessage = e.toString();
notifyListeners();
return null;
}
}
Future<bool> enable2FA(String code) async {
try {
await _service.enable2FA(code);
await loadSettings();
return true;
} catch (e) {
AppLogger.d('启用2FA失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 禁用2FA
Future<bool> disable2FA(String code) async {
try {
await _service.disable2FA(code);
await loadSettings();
return true;
} catch (e) {
AppLogger.d('禁用2FA失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 更新版本保留设置
Future<bool> updateVersionRetention({
bool? enabled,
List<String>? ext,
int? max,
}) async {
try {
await _service.updateUserSetting(
versionRetentionEnabled: enabled,
versionRetentionExt: ext,
versionRetentionMax: max,
);
await loadSettings();
return true;
} catch (e) {
AppLogger.d('更新版本保留设置失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 更新视图同步
Future<bool> updateViewSync(bool disabled) async {
try {
await _service.updateUserSetting(disableViewSync: disabled);
if (_settings != null) {
_settings = _settings!.copyWith(disableViewSync: disabled);
}
notifyListeners();
return true;
} catch (e) {
AppLogger.d('更新视图同步设置失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 更新分享链接可见性
Future<bool> updateShareLinksInProfile(String value) async {
try {
await _service.updateUserSetting(shareLinksInProfile: value);
if (_settings != null) {
_settings = _settings!.copyWith(shareLinksInProfile: value);
}
notifyListeners();
return true;
} catch (e) {
AppLogger.d('更新分享链接可见性失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 撤销OAuth授权
Future<bool> revokeOAuthGrant(String appId) async {
try {
await _service.revokeOAuthGrant(appId);
await loadSettings();
return true;
} catch (e) {
AppLogger.d('撤销OAuth授权失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 解绑OIDC提供商
Future<bool> unlinkOpenId(int provider) async {
try {
await _service.unlinkOpenId(provider);
await loadSettings();
return true;
} catch (e) {
AppLogger.d('解绑OIDC失败: $e');
_errorMessage = e.toString();
notifyListeners();
return false;
}
}
/// 清除错误
void clearError() {
_errorMessage = null;
notifyListeners();
}
void _setState(UserSettingState state) {
_state = state;
notifyListeners();
}
}