162 lines
5.2 KiB
Dart
162 lines
5.2 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:ttstd_family_care/core/network/error_message.dart';
|
||
import 'package:ttstd_family_care/features/account/data/account_providers.dart';
|
||
|
||
/// 账号与安全写操作状态。
|
||
///
|
||
/// [alert] 为一次性提示,UI 展示后需调用 [AccountController.consumeAlert] 消费;
|
||
/// [countdown] 为更换手机号场景的验证码倒计时秒数(0 表示可再次发送)。
|
||
class AccountActionState {
|
||
const AccountActionState({
|
||
this.submitting = false,
|
||
this.sendingCode = false,
|
||
this.countdown = 0,
|
||
this.alert,
|
||
});
|
||
|
||
final bool submitting;
|
||
final bool sendingCode;
|
||
final int countdown;
|
||
final String? alert;
|
||
|
||
AccountActionState copyWith({
|
||
bool? submitting,
|
||
bool? sendingCode,
|
||
int? countdown,
|
||
String? alert,
|
||
}) =>
|
||
AccountActionState(
|
||
submitting: submitting ?? this.submitting,
|
||
sendingCode: sendingCode ?? this.sendingCode,
|
||
countdown: countdown ?? this.countdown,
|
||
alert: alert,
|
||
);
|
||
}
|
||
|
||
/// 账号与安全控制器:修改密码、更换手机、微信绑定、登录设备下线。
|
||
///
|
||
/// 页面级控制器,**不使用** keepAlive;页面销毁时自动取消倒计时定时器。
|
||
class AccountController extends Notifier<AccountActionState> {
|
||
Timer? _timer;
|
||
|
||
@override
|
||
AccountActionState build() {
|
||
ref.onDispose(() => _timer?.cancel());
|
||
return const AccountActionState();
|
||
}
|
||
|
||
/// 消费一次性提示,避免重复弹窗。
|
||
void consumeAlert() {
|
||
if (state.alert != null) state = state.copyWith(alert: null);
|
||
}
|
||
|
||
/// 修改密码,成功返回 true。
|
||
Future<bool> changePassword({
|
||
required String oldPassword,
|
||
required String newPassword,
|
||
}) =>
|
||
_run(() => ref.read(accountRepositoryProvider).changePassword(
|
||
oldPassword: oldPassword,
|
||
newPassword: newPassword,
|
||
));
|
||
|
||
/// 发送更换手机号验证码,成功后启动 60 秒倒计时。
|
||
Future<bool> sendChangeMobileCode(String newMobile) async {
|
||
if (state.sendingCode || state.countdown > 0) return false;
|
||
state = state.copyWith(sendingCode: true, alert: null);
|
||
try {
|
||
final error =
|
||
await ref.read(accountRepositoryProvider).sendChangeMobileCode(
|
||
newMobile.trim(),
|
||
);
|
||
if (error != null) {
|
||
state = state.copyWith(sendingCode: false, alert: error);
|
||
return false;
|
||
}
|
||
state = state.copyWith(sendingCode: false, countdown: 60);
|
||
_startCountdown();
|
||
return true;
|
||
} catch (e) {
|
||
state = state.copyWith(sendingCode: false, alert: userMessageOf(e));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// 更换绑定手机号,成功返回 true。
|
||
Future<bool> changeMobile({
|
||
required String newMobile,
|
||
required String code,
|
||
required String password,
|
||
}) =>
|
||
_run(() => ref.read(accountRepositoryProvider).changeMobile(
|
||
newMobile: newMobile.trim(),
|
||
code: code.trim(),
|
||
password: password,
|
||
));
|
||
|
||
/// 绑定微信([authCode] 由微信 SDK 授权回调提供),成功返回 true。
|
||
Future<bool> bindWechat(String authCode) => _run(
|
||
() => ref.read(accountRepositoryProvider).bindWechat(authCode),
|
||
invalidateWechat: true,
|
||
);
|
||
|
||
/// 解绑微信,成功返回 true。
|
||
Future<bool> unbindWechat() => _run(
|
||
() => ref.read(accountRepositoryProvider).unbindWechat(),
|
||
invalidateWechat: true,
|
||
);
|
||
|
||
/// 将指定登录设备下线,成功返回 true。
|
||
Future<bool> revokeLoginDevice(String id) => _run(
|
||
() => ref.read(accountRepositoryProvider).revokeLoginDevice(id),
|
||
invalidateLoginDevices: true,
|
||
);
|
||
|
||
/// 每秒递减倒计时,归零后自动停止。
|
||
void _startCountdown() {
|
||
_timer?.cancel();
|
||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||
final next = state.countdown - 1;
|
||
if (next <= 0) {
|
||
timer.cancel();
|
||
state = state.copyWith(countdown: 0, alert: state.alert);
|
||
} else {
|
||
state = state.copyWith(countdown: next, alert: state.alert);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// 统一执行写操作:管理 submitting 开关、错误文案与相关列表刷新。
|
||
Future<bool> _run(
|
||
Future<String?> Function() action, {
|
||
bool invalidateWechat = false,
|
||
bool invalidateLoginDevices = false,
|
||
}) async {
|
||
if (state.submitting) return false;
|
||
state = state.copyWith(submitting: true, alert: null);
|
||
try {
|
||
final error = await action();
|
||
if (error != null) {
|
||
state = state.copyWith(submitting: false, alert: error);
|
||
return false;
|
||
}
|
||
if (invalidateWechat) ref.invalidate(wechatBindingProvider);
|
||
if (invalidateLoginDevices) ref.invalidate(loginDevicesProvider);
|
||
state = state.copyWith(submitting: false);
|
||
return true;
|
||
} catch (e) {
|
||
// 统一文案映射,禁止直接 toString()(见 AGENTS.md §12)。
|
||
state = state.copyWith(submitting: false, alert: userMessageOf(e));
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 账号与安全控制器 Provider(页面级,不 keepAlive)。
|
||
final accountControllerProvider =
|
||
NotifierProvider<AccountController, AccountActionState>(
|
||
AccountController.new,
|
||
);
|