mnemo_cards/mnemo_cards_web_v2/lib/app.dart

327 lines
9.7 KiB
Dart
Raw Normal View History

2025-11-10 23:55:41 +00:00
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:mnemo_cards_web_v2/di/user_scope/user_scope.dart';
import 'package:yx_scope_flutter/yx_scope_flutter.dart';
import 'package:yx_state_flutter/yx_state_flutter.dart';
import 'di/app_scope/app_scope_container.dart';
import 'di/app_scope/app_scope_holder.dart';
import 'domain/state/theme_state_manager.dart';
import 'main.dart' show scaffoldMessengerKey;
2025-12-18 23:17:03 +00:00
import 'presentation/theme/app_colors.dart';
2025-11-10 23:55:41 +00:00
import 'presentation/theme/app_theme.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
/// Main application widget
///
/// Wraps the entire app in ScopeProviders for AppScope and UserScope
class App extends StatelessWidget {
2025-12-20 18:26:15 +00:00
const App({required this.appScopeHolder, super.key});
2025-11-10 23:55:41 +00:00
final AppScopeHolder appScopeHolder;
@override
Widget build(BuildContext context) {
return ScopeProvider<AppScopeContainer>(
holder: appScopeHolder,
child: ScopeBuilder<AppScopeContainer>.withPlaceholder(
builder: (context, appScope) {
return _AppInitializer(appScope: appScope);
},
2025-12-18 23:17:03 +00:00
placeholder: const _LoadingPlaceholder(message: 'Инициализация...'),
2025-11-10 23:55:41 +00:00
),
);
}
}
/// Initializes the app and handles auto-login
class _AppInitializer extends StatefulWidget {
const _AppInitializer({required this.appScope});
final AppScopeContainer appScope;
@override
State<_AppInitializer> createState() => _AppInitializerState();
}
class _AppInitializerState extends State<_AppInitializer> {
bool _isInitialized = false;
@override
void initState() {
super.initState();
_initialize();
}
@override
void didUpdateWidget(_AppInitializer oldWidget) {
super.didUpdateWidget(oldWidget);
// Rebuild when UserScope changes
if (mounted) {
setState(() {});
}
}
2025-12-17 00:41:47 +00:00
/// Try Telegram Web App authentication
/// Returns true if authentication was successful, false otherwise
Future<bool> _tryTelegramWebAppAuth() async {
2025-11-27 21:06:26 +00:00
try {
log('Checking Telegram Web App authentication...', name: 'App');
// Try to authenticate with Telegram Web App
final user = await widget.appScope.authService.loginWithTelegramWebApp();
if (user != null) {
log('Telegram Web App authentication successful', name: 'App');
// Create UserScope if it doesn't exist
if (widget.appScope.userScopeHolder.scope == null) {
await widget.appScope.userScopeHolder.create();
}
// Set authenticated user
final userScope = widget.appScope.userScopeHolder.scope;
if (userScope != null) {
2025-12-14 19:21:36 +00:00
await userScope.userStateManager.setUser(user);
2025-11-27 21:06:26 +00:00
// Notify router about auth change
widget.appScope.userScopeHolder.notifyAuthChanged();
}
2025-12-17 00:41:47 +00:00
// Return true to indicate successful authentication
return true;
2025-11-27 21:06:26 +00:00
} else {
2025-12-20 18:26:15 +00:00
log(
'Telegram Web App authentication not available or failed',
name: 'App',
);
2025-12-17 00:41:47 +00:00
return false;
2025-11-27 21:06:26 +00:00
}
} catch (e, s) {
2025-12-20 18:26:15 +00:00
log(
'Error during Telegram Web App authentication',
error: e,
stackTrace: s,
);
2025-11-27 21:06:26 +00:00
// Continue with normal flow - this is not a critical error
2025-12-17 00:41:47 +00:00
return false;
2025-11-27 21:06:26 +00:00
}
}
2025-11-10 23:55:41 +00:00
Future<void> _initialize() async {
try {
log('Starting app initialization...', name: 'App');
2025-11-27 21:06:26 +00:00
// Try Telegram Web App authentication first
2025-12-17 00:41:47 +00:00
final telegramAuthSuccess = await _tryTelegramWebAppAuth();
2025-11-27 21:06:26 +00:00
2025-12-17 00:41:47 +00:00
// If Telegram Web App auth succeeded, skip normal auto-login
// (tokens are already saved and user is set)
if (telegramAuthSuccess) {
2025-12-20 18:26:15 +00:00
log(
'Telegram Web App auth succeeded, skipping normal auto-login',
name: 'App',
);
2025-11-10 23:55:41 +00:00
} else {
2025-12-17 00:41:47 +00:00
// Try auto-login with timeout
log('Attempting auto-login...', name: 'App');
UserDto? user;
try {
user = await widget.appScope.authService.autoLogin().timeout(
const Duration(seconds: 3),
onTimeout: () {
log('Auto-login timed out, continuing as guest', name: 'App');
return null;
},
);
} catch (e) {
2025-12-20 18:26:15 +00:00
log(
'Auto-login failed with error, continuing as guest',
error: e,
name: 'App',
);
2025-12-17 00:41:47 +00:00
user = null;
}
// Create UserScope if needed
if (widget.appScope.userScopeHolder.scope == null) {
log('Creating UserScope...', name: 'App');
await widget.appScope.userScopeHolder.create();
}
if (user != null) {
log('Auto-login successful, setting user', name: 'App');
2025-12-20 18:26:15 +00:00
await widget.appScope.userScopeHolder.scope!.userStateManager.setUser(
user,
);
2025-12-17 00:41:47 +00:00
// Notify router about auth change
widget.appScope.userScopeHolder.notifyAuthChanged();
log('UserScope created and user set', name: 'App');
} else {
log('No saved session, starting as guest', name: 'App');
}
2025-11-10 23:55:41 +00:00
}
log('Setting _isInitialized = true', name: 'App');
setState(() {
_isInitialized = true;
});
log('App initialization completed', name: 'App');
} catch (e) {
log('Error during initialization', error: e, name: 'App');
// Continue as guest even if auto-login fails
log('Continuing as guest after error', name: 'App');
setState(() {
_isInitialized = true;
});
}
}
@override
Widget build(BuildContext context) {
2025-12-20 18:26:15 +00:00
log(
'_AppInitializer build called, _isInitialized: $_isInitialized',
name: 'App',
);
2025-11-10 23:55:41 +00:00
if (!_isInitialized) {
log('Showing loading screen', name: 'App');
2025-12-18 23:17:03 +00:00
return _LoadingScreen(appScope: widget.appScope, message: 'Loading...');
2025-11-10 23:55:41 +00:00
}
log('Building main app', name: 'App');
return StateBuilder(
stateReadable: widget.appScope.themeManager,
builder: (context, themeState, _) {
2025-12-20 18:26:15 +00:00
log(
'StateBuilder builder called with themeState: ${themeState.mode}',
name: 'App',
);
2025-11-10 23:55:41 +00:00
return _buildAppWithUserScope(themeState);
},
);
}
Widget _buildAppWithUserScope(ThemeState themeState) {
Widget app = MaterialApp.router(
title: 'Mnemo Cards',
routerConfig: widget.appScope.router,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: themeState.mode,
scaffoldMessengerKey: scaffoldMessengerKey,
debugShowCheckedModeBanner: false,
);
// Wrap with UserScope provider only if UserScope exists
if (widget.appScope.userScopeHolder.scope != null) {
return ScopeProvider<UserScope>(
holder: widget.appScope.userScopeHolder,
child: app,
);
}
// For guest users, return app without UserScope provider
return app;
}
}
2025-12-18 23:17:03 +00:00
/// Плейсхолдер экрана загрузки (без доступа к теме)
class _LoadingPlaceholder extends StatelessWidget {
const _LoadingPlaceholder({required this.message});
2025-11-10 23:55:41 +00:00
final String message;
@override
Widget build(BuildContext context) {
return MaterialApp(
2025-12-18 23:17:03 +00:00
theme: AppTheme.light,
2025-11-10 23:55:41 +00:00
home: Scaffold(
2025-12-18 23:17:03 +00:00
backgroundColor: AppTheme.light.scaffoldBackgroundColor,
2025-11-10 23:55:41 +00:00
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
2025-12-20 18:26:15 +00:00
Image.asset('assets/images/el_sol.png', width: 120, height: 120),
2025-11-10 23:55:41 +00:00
const SizedBox(height: 24),
Text(
message,
2025-12-18 23:17:03 +00:00
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppColors.black,
),
2025-11-10 23:55:41 +00:00
),
],
),
),
),
);
}
}
2025-12-18 23:17:03 +00:00
/// Экран загрузки
class _LoadingScreen extends StatelessWidget {
2025-12-20 18:26:15 +00:00
const _LoadingScreen({required this.appScope, required this.message});
2025-12-18 23:17:03 +00:00
final AppScopeContainer appScope;
final String message;
@override
Widget build(BuildContext context) {
return FutureBuilder<ThemeState>(
future: _getThemeState(),
builder: (context, snapshot) {
final themeState = snapshot.data ?? ThemeState(ThemeMode.system);
2025-12-20 18:26:15 +00:00
final isDark =
themeState.mode == ThemeMode.dark ||
2025-12-18 23:17:03 +00:00
(themeState.mode == ThemeMode.system &&
2025-12-20 18:26:15 +00:00
MediaQuery.platformBrightnessOf(context) == Brightness.dark);
2025-12-18 23:17:03 +00:00
return MaterialApp(
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: themeState.mode,
home: Scaffold(
backgroundColor: isDark
? AppTheme.dark.scaffoldBackgroundColor
: AppTheme.light.scaffoldBackgroundColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
isDark
? 'assets/images/la_luna.png'
: 'assets/images/el_sol.png',
width: 120,
height: 120,
),
const SizedBox(height: 24),
Text(
message,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: isDark ? AppColors.white : AppColors.black,
),
),
],
),
),
),
);
},
);
}
Future<ThemeState> _getThemeState() async {
try {
return appScope.themeManager.state;
} catch (e) {
// Fallback to system theme if themeManager is not available
return ThemeState(ThemeMode.system);
}
}
}