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;
|
|
|
|
|
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 {
|
|
|
|
|
const App({
|
|
|
|
|
required this.appScopeHolder,
|
|
|
|
|
super.key,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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-02 21:43:49 +00:00
|
|
|
placeholder: const _LoadingScreen(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 {
|
|
|
|
|
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) {
|
|
|
|
|
log('Error during Telegram Web App authentication', error: e, stackTrace: s);
|
|
|
|
|
// 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) {
|
|
|
|
|
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) {
|
|
|
|
|
log('Auto-login failed with error, continuing as guest', error: e, name: 'App');
|
|
|
|
|
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');
|
|
|
|
|
await widget.appScope.userScopeHolder.scope!.userStateManager.setUser(user);
|
|
|
|
|
// 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
|
|
|
}
|
|
|
|
|
|
2025-12-17 00:41:47 +00:00
|
|
|
|
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) {
|
|
|
|
|
log('_AppInitializer build called, _isInitialized: $_isInitialized', name: 'App');
|
|
|
|
|
|
|
|
|
|
if (!_isInitialized) {
|
|
|
|
|
log('Showing loading screen', name: 'App');
|
2025-12-02 22:16:35 +00:00
|
|
|
return const _LoadingScreen(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, _) {
|
|
|
|
|
log('StateBuilder builder called with themeState: ${themeState.mode}', name: 'App');
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Экран загрузки
|
|
|
|
|
class _LoadingScreen extends StatelessWidget {
|
|
|
|
|
const _LoadingScreen({required this.message});
|
|
|
|
|
|
|
|
|
|
final String message;
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
|
return MaterialApp(
|
|
|
|
|
home: Scaffold(
|
2025-12-02 22:50:26 +00:00
|
|
|
backgroundColor: Theme.of(context).colorScheme.brightness == Brightness.dark ? AppTheme.dark.scaffoldBackgroundColor : AppTheme.light.scaffoldBackgroundColor,
|
2025-11-10 23:55:41 +00:00
|
|
|
body: Center(
|
|
|
|
|
child: Column(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
const CircularProgressIndicator(),
|
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
|
Text(
|
|
|
|
|
message,
|
|
|
|
|
style: const TextStyle(fontSize: 18),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|