feat(account): 增加账号中心页面逻辑
This commit is contained in:
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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user