feat(account): 增加账号中心页面逻辑

This commit is contained in:
Zhou Weidong committed 2026-10-19 09:48:07 +08:00
1 parent ef7f4ce3b6
commit c68f02449f
6 files changed
+503

No files matched your search

@@ -0,0 +1,85 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import '../../core/data/remote/api_client.dart';
import '../../core/data/repositories/account_repository.dart';
/// 注销申请与撤销状态。
class AccountDeactivateLogic extends ChangeNotifier {
AccountDeactivateLogic(
this._repository, {
required this.clearSession,
});
static const maxReasonLength = 200;
final AccountRepository _repository;
final Future<void> Function() clearSession;
DateTime? scheduledAt;
bool requesting = false;
bool cancelling = false;
String? error;
String? actionMessage;
bool cancelSucceeded = false;
bool _disposed = false;
/// 申请注销;成功后立即清理本地会话。
Future<bool> request(String reason) async {
requesting = true;
error = null;
cancelSucceeded = false;
_notify();
try {
scheduledAt = await _repository.deactivate(
reason.trim().substring(
0,
reason.trim().length > maxReasonLength
? maxReasonLength
: reason.trim().length,
),
);
await clearSession();
return true;
} catch (failure) {
error = failure is ApiException
? failure.message
: 'accountDeactivateFailed';
return false;
} finally {
requesting = false;
_notify();
}
}
/// 撤销待处理申请;失败时保留后端文案。
Future<bool> cancel() async {
cancelling = true;
actionMessage = null;
cancelSucceeded = false;
_notify();
try {
await _repository.cancelDeactivation();
cancelSucceeded = true;
return true;
} catch (failure) {
actionMessage = failure is ApiException
? failure.message
: 'accountCancelFailed';
return false;
} finally {
cancelling = false;
_notify();
}
}
void _notify() {
if (!_disposed) notifyListeners();
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
@@ -0,0 +1,45 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import '../../core/data/models/account_device.dart';
import '../../core/data/remote/api_client.dart';
import '../../core/data/repositories/account_repository.dart';
/// 只读登录设备列表状态。
class AccountDevicesLogic extends ChangeNotifier {
AccountDevicesLogic(this._repository);
final AccountRepository _repository;
List<AccountDevice> devices = const [];
bool loading = false;
String? error;
bool _disposed = false;
/// 拉取当前账号的设备列表。
Future<void> load() async {
loading = true;
error = null;
_notify();
try {
devices = await _repository.devices();
} catch (failure) {
error = failure is ApiException
? failure.message
: 'accountDevicesLoadFailed';
} finally {
loading = false;
_notify();
}
}
void _notify() {
if (!_disposed) notifyListeners();
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
@@ -0,0 +1,54 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import '../../core/data/models/account_export.dart';
import '../../core/data/remote/api_client.dart';
import '../../core/data/repositories/account_repository.dart';
/// 账号数据导出状态与复制动作。
class AccountExportLogic extends ChangeNotifier {
AccountExportLogic(this._repository, {required this.copyText});
final AccountRepository _repository;
final Future<void> Function(String text) copyText;
AccountExport? data;
bool loading = false;
String? error;
bool _disposed = false;
/// 加载完整导出数据。
Future<void> load() async {
loading = true;
error = null;
_notify();
try {
data = await _repository.export();
} catch (failure) {
error = failure is ApiException
? failure.message
: 'accountExportLoadFailed';
} finally {
loading = false;
_notify();
}
}
/// 把后端下发的完整 JSON 写入剪贴板。
Future<bool> copy() async {
final current = data;
if (current == null) return false;
await copyText(current.rawJson);
return true;
}
void _notify() {
if (!_disposed) notifyListeners();
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}
@@ -0,0 +1,91 @@
// Author: fruit.com.cn
import 'package:flutter/foundation.dart';
import '../../core/data/models/account_profile.dart';
import '../../core/data/remote/api_client.dart';
import '../../core/data/repositories/account_repository.dart';
/// 个人资料页状态与保存动作。
class AccountProfileLogic extends ChangeNotifier {
AccountProfileLogic(this._repository, {required this.onProfileUpdated});
static const maxNicknameLength = 30;
final AccountRepository _repository;
final Future<void> Function(AccountProfile profile) onProfileUpdated;
AccountProfile? profile;
bool loading = false;
bool saving = false;
String? error;
String? fieldError;
bool _disposed = false;
/// 进入页面时加载当前资料。
Future<void> load() async {
loading = true;
error = null;
_notify();
try {
profile = await _repository.profile();
} catch (failure) {
error = _message(failure) ?? 'accountProfileLoadFailed';
} finally {
loading = false;
_notify();
}
}
/// 只提交本次改动;成功后同步登录态摘要。
Future<bool> save({
required String nickname,
required String gender,
String? birthday,
}) async {
final current = profile;
if (current == null) return false;
final trimmed = nickname.trim();
if (trimmed.isEmpty || trimmed.length > maxNicknameLength) {
fieldError = 'accountNicknameInvalid';
_notify();
return false;
}
final update = AccountProfileUpdate(
nickname: trimmed == current.nickname ? null : trimmed,
gender: gender.isEmpty || gender == current.gender ? null : gender,
birthday: birthday == null || birthday.isEmpty ? null : birthday,
);
if (update.toJson().isEmpty) return true;
saving = true;
error = null;
fieldError = null;
_notify();
try {
final next = await _repository.updateProfile(update);
profile = next;
await onProfileUpdated(next);
return true;
} catch (failure) {
error = _message(failure) ?? 'accountProfileSaveFailed';
return false;
} finally {
saving = false;
_notify();
}
}
String? _message(Object failure) =>
failure is ApiException ? failure.message : null;
void _notify() {
if (!_disposed) notifyListeners();
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
}