Template
chore: 初始化脚手架 — sunny_mochi 企业级 Flutter 模板
Core 基础设施:Flavor 三包体系、Drift+SQLCipher 加密数据库 7 拦截器 Dio 网络栈、CrashReporter 崩溃日志、Sealed Failure 错误体系 5 色板主题系统、Auth 认证骨架(Clean Architecture)、Dev Panel、Mock Adapter 通过验证:flutter analyze(0 errors)、flutter test(全绿)、staging APK 74.8MB
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sunny_mochi/core/error/exception_mapper.dart';
|
||||
import 'package:sunny_mochi/features/auth/auth_providers.dart';
|
||||
import 'package:sunny_mochi/features/auth/domain/entities/user_entity.dart';
|
||||
import 'package:sunny_mochi/features/auth/presentation/notifiers/auth_status_provider.dart';
|
||||
|
||||
part 'auth_notifier.freezed.dart';
|
||||
part 'auth_notifier.g.dart';
|
||||
|
||||
@freezed
|
||||
sealed class AuthState with _$AuthState {
|
||||
const factory AuthState.initial() = AuthInitial;
|
||||
const factory AuthState.loading() = AuthLoading;
|
||||
const factory AuthState.authenticated(UserEntity user) = AuthAuthenticated;
|
||||
const factory AuthState.unauthenticated() = AuthUnauthenticated;
|
||||
const factory AuthState.error(String message) = AuthError;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class AuthNotifier extends _$AuthNotifier {
|
||||
static const _mapper = ExceptionMapper();
|
||||
|
||||
// generation 计数:防止旧 async 回调覆盖最新登录态
|
||||
int _generation = 0;
|
||||
|
||||
@override
|
||||
AuthState build() {
|
||||
final gen = ++_generation;
|
||||
unawaited(_checkAuth(gen));
|
||||
return const AuthState.initial();
|
||||
}
|
||||
|
||||
Future<void> _checkAuth(int gen) async {
|
||||
final user = await ref.read(authRepositoryProvider).getCurrentUser();
|
||||
if (_generation != gen) return;
|
||||
if (user != null) {
|
||||
state = AuthState.authenticated(user);
|
||||
ref.read(authStatusProvider).markLoggedIn();
|
||||
unawaited(refreshUserInfo());
|
||||
} else {
|
||||
state = const AuthState.unauthenticated();
|
||||
ref.read(authStatusProvider).markLoggedOut();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loginWithPhone(String phone, String code) async {
|
||||
_generation++;
|
||||
state = const AuthState.loading();
|
||||
try {
|
||||
final user = await ref
|
||||
.read(authRepositoryProvider)
|
||||
.loginWithPhone(phone: phone, code: code);
|
||||
state = AuthState.authenticated(user);
|
||||
ref.read(authStatusProvider).markLoggedIn();
|
||||
unawaited(refreshUserInfo());
|
||||
} on Object catch (e, st) {
|
||||
final failure = _mapper.fromUnknown(e, st);
|
||||
state = AuthState.error(failure.message);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loginWithPassword(String phone, String password) async {
|
||||
_generation++;
|
||||
state = const AuthState.loading();
|
||||
try {
|
||||
final user = await ref
|
||||
.read(authRepositoryProvider)
|
||||
.loginWithPassword(phone: phone, password: password);
|
||||
state = AuthState.authenticated(user);
|
||||
ref.read(authStatusProvider).markLoggedIn();
|
||||
unawaited(refreshUserInfo());
|
||||
} on Object catch (e, st) {
|
||||
final failure = _mapper.fromUnknown(e, st);
|
||||
state = AuthState.error(failure.message);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
_generation++;
|
||||
state = const AuthState.unauthenticated();
|
||||
ref.read(authStatusProvider).markLoggedOut();
|
||||
unawaited(ref.read(authRepositoryProvider).logout());
|
||||
}
|
||||
|
||||
/// 发送短信验证码。返回 dev/test 环境后端回显的验证码(release 为 null),供 UI 自动填入。
|
||||
Future<String?> sendSmsCode(String phone) async {
|
||||
try {
|
||||
return await ref.read(authRepositoryProvider).sendSmsCode(phone);
|
||||
} on Object catch (e, st) {
|
||||
throw _mapper.fromUnknown(e, st);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> refreshUserInfo() async {
|
||||
final current = state;
|
||||
if (current is! AuthAuthenticated) return;
|
||||
final gen = _generation;
|
||||
try {
|
||||
final fresh = await ref.read(authRepositoryProvider).fetchUserProfile();
|
||||
if (_generation != gen) return;
|
||||
state = AuthState.authenticated(
|
||||
fresh.copyWith(
|
||||
token: current.user.token,
|
||||
userId: fresh.userId ?? current.user.userId,
|
||||
),
|
||||
);
|
||||
} on Object {
|
||||
// profile 拉取失败不应改变登录状态
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user