Files
ttstd_family_care/lib/features/account/presentation/account_controller.dart
2026-08-21 17:12:17 +08:00

162 lines
5.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
);