mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
Backend fixes: - Fix duplicate AssistantResponseJob triggering causing duplicate AI responses - UserMessage model already handles job triggering via after_create_commit callback - Remove redundant job enqueue in chats_controller and messages_controller Mobile app features: - Implement complete AI chat interface and conversation management - Add Chat, Message, and ToolCall data models - Add ChatProvider for state management with polling mechanism - Add ChatService to handle all chat-related API requests - Add chat list screen (ChatListScreen) - Add conversation detail screen (ChatConversationScreen) - Refactor navigation structure with bottom navigation bar (MainNavigationScreen) - Add settings screen (SettingsScreen) - Optimize TransactionsProvider to support account filtering Technical details: - Implement message polling mechanism for real-time AI responses - Support chat creation, deletion, retry and other operations - Integrate Material Design 3 design language - Improve user experience and error handling Co-authored-by: dwvwdv <dwvwdv@protonmail.com>
216 lines
6.2 KiB
Dart
216 lines
6.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'providers/auth_provider.dart';
|
|
import 'providers/accounts_provider.dart';
|
|
import 'providers/transactions_provider.dart';
|
|
import 'providers/chat_provider.dart';
|
|
import 'screens/backend_config_screen.dart';
|
|
import 'screens/login_screen.dart';
|
|
import 'screens/main_navigation_screen.dart';
|
|
import 'services/api_config.dart';
|
|
import 'services/connectivity_service.dart';
|
|
import 'services/log_service.dart';
|
|
|
|
void main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
await ApiConfig.initialize();
|
|
|
|
// Add initial log entry
|
|
LogService.instance.info('App', 'Sure Finance app starting...');
|
|
|
|
runApp(const SureApp());
|
|
}
|
|
|
|
class SureApp extends StatelessWidget {
|
|
const SureApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider(create: (_) => LogService.instance),
|
|
ChangeNotifierProvider(create: (_) => ConnectivityService()),
|
|
ChangeNotifierProvider(create: (_) => AuthProvider()),
|
|
ChangeNotifierProvider(create: (_) => ChatProvider()),
|
|
ChangeNotifierProxyProvider<ConnectivityService, AccountsProvider>(
|
|
create: (_) => AccountsProvider(),
|
|
update: (_, connectivityService, accountsProvider) {
|
|
if (accountsProvider == null) {
|
|
final provider = AccountsProvider();
|
|
provider.setConnectivityService(connectivityService);
|
|
return provider;
|
|
} else {
|
|
accountsProvider.setConnectivityService(connectivityService);
|
|
return accountsProvider;
|
|
}
|
|
},
|
|
),
|
|
ChangeNotifierProxyProvider<ConnectivityService, TransactionsProvider>(
|
|
create: (_) => TransactionsProvider(),
|
|
update: (_, connectivityService, transactionsProvider) {
|
|
if (transactionsProvider == null) {
|
|
final provider = TransactionsProvider();
|
|
provider.setConnectivityService(connectivityService);
|
|
return provider;
|
|
} else {
|
|
transactionsProvider.setConnectivityService(connectivityService);
|
|
return transactionsProvider;
|
|
}
|
|
},
|
|
),
|
|
],
|
|
child: MaterialApp(
|
|
title: 'Sure Finance',
|
|
debugShowCheckedModeBanner: false,
|
|
theme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: const Color(0xFF6366F1),
|
|
brightness: Brightness.light,
|
|
),
|
|
useMaterial3: true,
|
|
appBarTheme: const AppBarTheme(
|
|
centerTitle: true,
|
|
elevation: 0,
|
|
),
|
|
cardTheme: CardThemeData(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
filled: true,
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
minimumSize: const Size(double.infinity, 50),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
darkTheme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: const Color(0xFF6366F1),
|
|
brightness: Brightness.dark,
|
|
),
|
|
useMaterial3: true,
|
|
appBarTheme: const AppBarTheme(
|
|
centerTitle: true,
|
|
elevation: 0,
|
|
),
|
|
cardTheme: CardThemeData(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
filled: true,
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
minimumSize: const Size(double.infinity, 50),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
themeMode: ThemeMode.system,
|
|
routes: {
|
|
'/config': (context) => const BackendConfigScreen(),
|
|
'/login': (context) => const LoginScreen(),
|
|
'/home': (context) => const MainNavigationScreen(),
|
|
},
|
|
home: const AppWrapper(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class AppWrapper extends StatefulWidget {
|
|
const AppWrapper({super.key});
|
|
|
|
@override
|
|
State<AppWrapper> createState() => _AppWrapperState();
|
|
}
|
|
|
|
class _AppWrapperState extends State<AppWrapper> {
|
|
bool _isCheckingConfig = true;
|
|
bool _hasBackendUrl = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_checkBackendConfig();
|
|
}
|
|
|
|
Future<void> _checkBackendConfig() async {
|
|
final hasUrl = await ApiConfig.initialize();
|
|
if (mounted) {
|
|
setState(() {
|
|
_hasBackendUrl = hasUrl;
|
|
_isCheckingConfig = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
void _onBackendConfigSaved() {
|
|
setState(() {
|
|
_hasBackendUrl = true;
|
|
});
|
|
}
|
|
|
|
void _goToBackendConfig() {
|
|
setState(() {
|
|
_hasBackendUrl = false;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (_isCheckingConfig) {
|
|
return const Scaffold(
|
|
body: Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!_hasBackendUrl) {
|
|
return BackendConfigScreen(
|
|
onConfigSaved: _onBackendConfigSaved,
|
|
);
|
|
}
|
|
|
|
return Consumer<AuthProvider>(
|
|
builder: (context, authProvider, _) {
|
|
// Only show loading spinner during initial auth check
|
|
if (authProvider.isInitializing) {
|
|
return const Scaffold(
|
|
body: Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (authProvider.isAuthenticated) {
|
|
return const MainNavigationScreen();
|
|
}
|
|
|
|
return LoginScreen(
|
|
onGoToSettings: _goToBackendConfig,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|