feat: 切换邮箱认证并重构前后端启动与门禁
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
import '../core/auth/session_store.dart';
|
||||
import '../data/network/api_client.dart';
|
||||
import '../data/storage/local_kv_store.dart';
|
||||
import '../features/auth/data/apis/auth_api.dart';
|
||||
import '../features/auth/data/repositories/auth_repository.dart';
|
||||
import '../features/auth/presentation/bloc/auth_bloc.dart';
|
||||
import '../features/auth/presentation/bloc/auth_state.dart';
|
||||
import '../features/auth/presentation/screens/login_screen.dart';
|
||||
import '../features/home/presentation/screens/home_screen.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../shared/widgets/app_loading_indicator.dart';
|
||||
import 'app_theme.dart';
|
||||
import 'di/injection.dart';
|
||||
|
||||
class EryaoApp extends StatefulWidget {
|
||||
const EryaoApp({super.key});
|
||||
|
||||
@override
|
||||
State<EryaoApp> createState() => _EryaoAppState();
|
||||
}
|
||||
|
||||
class _EryaoAppState extends State<EryaoApp> {
|
||||
final SessionStore _sessionStore = SessionStore(LocalKvStore());
|
||||
late final AuthBloc _authBloc;
|
||||
Locale _locale = const Locale('zh');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final apiClient = ApiClient(
|
||||
baseUrl: appDependencies.backendUrl,
|
||||
tokenProvider: _sessionStore.getToken,
|
||||
onUnauthorized: () {
|
||||
return _authBloc.handleUnauthorized401();
|
||||
},
|
||||
);
|
||||
final authApi = AuthApi(apiClient: apiClient);
|
||||
final authRepository = AuthRepositoryImpl(
|
||||
authApi: authApi,
|
||||
sessionStore: _sessionStore,
|
||||
);
|
||||
_authBloc = AuthBloc(repository: authRepository);
|
||||
_bootstrap();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_authBloc.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _bootstrap() async {
|
||||
final localeCode = await _sessionStore.getLocaleCode();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_locale = localeCode == 'en' ? const Locale('en') : const Locale('zh');
|
||||
});
|
||||
}
|
||||
await _authBloc.start();
|
||||
}
|
||||
|
||||
Future<void> _handleLocaleChanged(Locale locale) async {
|
||||
await _sessionStore.saveLocaleCode(locale.languageCode);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_locale = locale;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _authBloc,
|
||||
builder: (context, _) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
|
||||
locale: _locale,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
theme: AppTheme.light(),
|
||||
home: _buildHomeByAuthState(_authBloc.state),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHomeByAuthState(AuthState state) {
|
||||
if (state.status == AuthStatus.initial ||
|
||||
state.status == AuthStatus.loading) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: AppLoadingIndicator(variant: AppLoadingVariant.surface),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.status == AuthStatus.authenticated && state.user != null) {
|
||||
return HomeScreen(
|
||||
account: state.user!.email,
|
||||
sessionStore: _sessionStore,
|
||||
onLogout: _authBloc.logout,
|
||||
);
|
||||
}
|
||||
|
||||
return LoginScreen(
|
||||
currentLocale: _locale,
|
||||
onLocaleChanged: _handleLocaleChanged,
|
||||
onRequestOtp: _authBloc.sendOtp,
|
||||
onLoginWithOtp: (email, otp) {
|
||||
return _authBloc.loginWithOtp(email: email, otp: otp);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../shared/theme/app_color_palette.dart';
|
||||
|
||||
class AppTheme {
|
||||
static const Color _primary = Color(0xFF673AB7);
|
||||
static const Color _accent = Color(0xFF9C27B0);
|
||||
static const Color _scaffold = Color(0xFFF8F8F8);
|
||||
static const Color _textHigh = Color(0xFF333333);
|
||||
static const Color _textMid = Color(0xFF666666);
|
||||
static const Color _textLow = Color(0xFF999999);
|
||||
|
||||
static ThemeData light() {
|
||||
const colorScheme = ColorScheme.light(
|
||||
primary: _primary,
|
||||
onPrimary: Color(0xFFFFFFFF),
|
||||
secondary: _accent,
|
||||
onSecondary: Color(0xFFFFFFFF),
|
||||
surface: Color(0xFFFFFFFF),
|
||||
onSurface: _textHigh,
|
||||
error: Color(0xFFB00020),
|
||||
onError: Color(0xFFFFFFFF),
|
||||
outline: Color(0xFFDDDDDD),
|
||||
surfaceContainerHighest: Color(0xFFF0E6FF),
|
||||
surfaceContainerHigh: Color(0xFFF4F5F7),
|
||||
surfaceContainerLow: Color(0xFFFAFAFA),
|
||||
);
|
||||
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: colorScheme,
|
||||
scaffoldBackgroundColor: _scaffold,
|
||||
textTheme: const TextTheme(
|
||||
headlineMedium: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: _textHigh,
|
||||
),
|
||||
titleLarge: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _textHigh,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _textHigh,
|
||||
),
|
||||
bodyLarge: TextStyle(fontSize: 16, color: _textMid),
|
||||
bodyMedium: TextStyle(fontSize: 14, color: _textMid),
|
||||
bodySmall: TextStyle(fontSize: 12, color: _textLow),
|
||||
),
|
||||
extensions: const <ThemeExtension<dynamic>>[
|
||||
AppColorPalette(
|
||||
accentPurple: _accent,
|
||||
historyGoldBg: Color(0xFFFFF8E1),
|
||||
historyGoldText: Color(0xFFFFB300),
|
||||
historyBlueBg: Color(0xFFE6F7FF),
|
||||
historyBlueText: Color(0xFF1890FF),
|
||||
historyGrayBg: Color(0xFFF5F5F5),
|
||||
historyGrayText: Color(0xFF9E9E9E),
|
||||
categoryCareerBg: Color(0xFFF0E6FF),
|
||||
categoryCareerText: Color(0xFF673AB7),
|
||||
categoryLoveBg: Color(0xFFFFF3E0),
|
||||
categoryLoveText: Color(0xFFFF9800),
|
||||
categoryMoneyBg: Color(0xFFE8F5E9),
|
||||
categoryMoneyText: Color(0xFF4CAF50),
|
||||
notificationDot: Color(0xFFE53935),
|
||||
warning: Color(0xFFF57C00),
|
||||
warningContainer: Color(0xFFFFF3E0),
|
||||
onWarningContainer: Color(0xFF8A4B00),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import '../../core/config/env.dart';
|
||||
|
||||
class AppDependencies {
|
||||
const AppDependencies({required this.backendUrl});
|
||||
|
||||
final String backendUrl;
|
||||
}
|
||||
|
||||
AppDependencies? _appDependencies;
|
||||
|
||||
AppDependencies get appDependencies {
|
||||
return _appDependencies ?? AppDependencies(backendUrl: Env.backendUrl);
|
||||
}
|
||||
|
||||
Future<void> configureDependencies() async {
|
||||
_appDependencies = AppDependencies(backendUrl: Env.backendUrl);
|
||||
}
|
||||
Reference in New Issue
Block a user