import 'package:flutter/material.dart'; import '../api/odoo_client.dart'; import '../services/session.dart'; import 'home_screen.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); runApp(const SaatInvestApp()); } class SaatInvestApp extends StatelessWidget { const SaatInvestApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Saat Invest', debugShowCheckedModeBanner: false, theme: ThemeData(colorSchemeSeed: const Color(0xFF0B6E4F), useMaterial3: true), home: const RootScreen(), ); } } /// Jika session tersimpan -> Home, jika tidak -> Login. class RootScreen extends StatefulWidget { const RootScreen({super.key}); @override State createState() => _RootScreenState(); } class _RootScreenState extends State { late Future<({String base, String db, String user, String pass})?> _check; @override void initState() { super.initState(); _check = Session.load(); } void _onLoginDone({required String base, required String db, required String user, required String pass}) { setState(() { _check = Future.value( (base: base, db: db, user: user, pass: pass), ); }); } Future _logout() async { await Session.clear(); setState(() => _check = Session.load()); } @override Widget build(BuildContext context) { return FutureBuilder( future: _check, builder: (context, snap) { if (snap.connectionState != ConnectionState.done) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } final s = snap.data; if (s == null) { return LoginScreen(onSuccess: _onLoginDone); } return HomeScreen( baseUrl: s.base, db: s.db, user: s.user, pass: s.pass, onLogout: _logout, ); }, ); } } // --------------------------------------------------------------------------- class LoginScreen extends StatefulWidget { const LoginScreen({super.key, required this.onSuccess}); final void Function({required String base, required String db, required String user, required String pass}) onSuccess; @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State { final _url = TextEditingController(); // mis http://:18069 final _db = TextEditingController(text: 'odoo'); final _user = TextEditingController(text: 'admin'); final _pass = TextEditingController(); bool _busy = false; String? _error; @override void initState() { super.initState(); _loadUrl(); } Future _loadUrl() async { final url = await Session.readLastUrl(); if (url != null && mounted) { setState(() => _url.text = url); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF0B6E4F), body: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Card( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Icon(Icons.trending_up, color: Color(0xFF0B6E4F), size: 40), const SizedBox(height: 8), Text('Saat Invest', textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineSmall), Text('Catat Jual-Beli Saham via Odoo', textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodySmall), const SizedBox(height: 20), TextField(controller: _url, decoration: const InputDecoration(labelText: 'URL Server Odoo', hintText: 'http://169.58.149.164:18069', prefixIcon: Icon(Icons.dns))), const SizedBox(height: 12), TextField(controller: _db, decoration: const InputDecoration(labelText: 'Database', prefixIcon: Icon(Icons.storage))), const SizedBox(height: 12), TextField(controller: _user, decoration: const InputDecoration(labelText: 'Username', prefixIcon: Icon(Icons.person))), const SizedBox(height: 12), TextField(controller: _pass, obscureText: true, decoration: const InputDecoration(labelText: 'Password', prefixIcon: Icon(Icons.lock))), if (_error != null) ...[ const SizedBox(height: 12), Text(_error!, style: const TextStyle(color: Colors.red)), ], const SizedBox(height: 20), FilledButton.icon( onPressed: _busy ? null : _login, icon: _busy ? const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)) : const Icon(Icons.login), label: const Text('Masuk'), ), ], ), ), ), ), ), ); } Future _login() async { setState(() { _busy = true; _error = null; }); String base = _url.text.trim(); if (base.isNotEmpty && !base.startsWith('http')) base = 'http://$base'; try { final c = OdooClient( baseUrl: base, db: _db.text.trim(), username: _user.text.trim(), password: _pass.text, ); await c.authenticate(); // hit; lempar bila gagal await Session.save(base, _db.text.trim(), _user.text.trim(), _pass.text); if (!mounted) return; widget.onSuccess(base: base, db: _db.text.trim(), user: _user.text.trim(), pass: _pass.text); } catch (e) { if (!mounted) return; setState(() { _busy = false; _error = e.toString().replaceFirst('Exception: ', ''); }); } } }