// lib/screens/config_screen.dart import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:cashumit/widgets/custom_text_config_dialog.dart'; import 'package:cashumit/services/firefly_api_service.dart'; import 'package:bluetooth_print/bluetooth_print.dart'; import 'package:bluetooth_print/bluetooth_print_model.dart'; class ConfigScreen extends StatefulWidget { const ConfigScreen({super.key}); @override State createState() => _ConfigScreenState(); } class _ConfigScreenState extends State { final _formKey = GlobalKey(); final _urlController = TextEditingController(); final _tokenController = TextEditingController(); bool _isTestingConnection = false; bool _isTestingAuth = false; // Bluetooth printer variables BluetoothPrint bluetoothPrint = BluetoothPrint.instance; bool _isScanning = false; List _devices = []; BluetoothDevice? _selectedDevice; bool _connected = false; static const String _prefsKeyUrl = 'firefly_url'; static const String _prefsKeyToken = 'firefly_token'; @override void initState() { super.initState(); _loadConfig(); _initBluetooth(); } /// Memuat konfigurasi yang tersimpan dari shared preferences. /// /// Metode ini dipanggil saat ConfigScreen pertama kali dibuat (initState) /// untuk mengisi field input dengan konfigurasi yang telah disimpan sebelumnya. Future _loadConfig() async { final prefs = await SharedPreferences.getInstance(); final savedUrl = prefs.getString(_prefsKeyUrl) ?? ''; final savedToken = prefs.getString(_prefsKeyToken) ?? ''; setState(() { _urlController.text = savedUrl; _tokenController.text = savedToken; }); } /// Inisialisasi Bluetooth printer Future _initBluetooth() async { // Periksa status koneksi final isConnected = await bluetoothPrint.isConnected ?? false; if (mounted) { setState(() { _connected = isConnected; }); } // Listen to bluetooth state changes bluetoothPrint.state.listen((state) { if (mounted) { switch (state) { case BluetoothPrint.connected: setState(() { _connected = true; }); break; case BluetoothPrint.disconnected: setState(() { _connected = false; _selectedDevice = null; }); break; default: break; } } }); // Load saved device await _loadSavedBluetoothDevice(); } /// Memuat device bluetooth yang tersimpan Future _loadSavedBluetoothDevice() async { final prefs = await SharedPreferences.getInstance(); final deviceAddress = prefs.getString('bluetooth_device_address'); final deviceName = prefs.getString('bluetooth_device_name'); if (deviceAddress != null && deviceName != null) { final device = BluetoothDevice(); device.name = deviceName; device.address = deviceAddress; if (mounted) { setState(() { _selectedDevice = device; }); } } } /// Menyimpan device bluetooth yang terhubung Future _saveBluetoothDevice(BluetoothDevice device) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('bluetooth_device_address', device.address ?? ''); await prefs.setString('bluetooth_device_name', device.name ?? ''); } /// Menyimpan konfigurasi ke shared preferences. /// /// Setelah menyimpan, metode ini akan kembali ke ReceiptScreen dengan hasil `true` /// untuk memberi tahu ReceiptScreen bahwa kredensial telah diperbarui dan perlu /// memuat ulang daftar akun. Future _saveConfig() async { if (_formKey.currentState!.validate()) { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_prefsKeyUrl, _urlController.text); await prefs.setString(_prefsKeyToken, _tokenController.text); // Menampilkan snackbar konfirmasi if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Konfigurasi tersimpan!')), ); // Kembali ke ReceiptScreen dan memberi tahu untuk memuat ulang data Navigator.pop(context, true); } } } /// Menguji koneksi ke Firefly III. /// /// Metode ini menguji apakah URL Firefly III yang dimasukkan pengguna /// dapat dijangkau dan merespons dengan benar. Future _testConnection() async { if (_urlController.text.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Silakan masukkan URL Firefly III terlebih dahulu')), ); } return; } setState(() { _isTestingConnection = true; }); try { final isConnected = await FireflyApiService.testConnection(baseUrl: _urlController.text); if (mounted) { if (isConnected) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Koneksi berhasil!')), ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Koneksi gagal. Periksa URL dan koneksi internet Anda.')), ); } } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Terjadi kesalahan saat menguji koneksi')), ); } } finally { if (mounted) { setState(() { _isTestingConnection = false; }); } } } /// Menguji autentikasi dengan token. /// /// Metode ini menguji apakah Personal Access Token yang dimasukkan pengguna /// valid dan dapat digunakan untuk mengakses API Firefly III. Future _testAuthentication() async { if (_urlController.text.isEmpty || _tokenController.text.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Silakan masukkan URL dan token Firefly III terlebih dahulu')), ); } return; } setState(() { _isTestingAuth = true; }); try { final isAuthenticated = await FireflyApiService.testAuthentication( baseUrl: _urlController.text, accessToken: _tokenController.text, ); if (mounted) { if (isAuthenticated) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Autentikasi berhasil!')), ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Autentikasi gagal. Periksa token Anda.')), ); } } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Terjadi kesalahan saat menguji autentikasi')), ); } } finally { if (mounted) { setState(() { _isTestingAuth = false; }); } } } /// Mulai scan perangkat Bluetooth Future _startScan() async { setState(() { _isScanning = true; _devices.clear(); }); try { await bluetoothPrint.startScan(timeout: const Duration(seconds: 4)); // Listen to scan results bluetoothPrint.scanResults.listen((devices) { if (mounted) { setState(() { _devices = devices; }); } }); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal memindai perangkat: $e')), ); } } finally { if (mounted) { setState(() { _isScanning = false; }); } } } /// Menghubungkan ke perangkat Bluetooth Future _connectToDevice(BluetoothDevice device) async { try { await bluetoothPrint.connect(device); // Simpan device yang terhubung await _saveBluetoothDevice(device); if (mounted) { setState(() { _selectedDevice = device; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Terhubung ke ${device.name}')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal terhubung ke ${device.name}: $e')), ); } } } /// Memutuskan koneksi dari printer bluetooth Future _disconnectPrinter() async { try { await bluetoothPrint.disconnect(); // Hapus device yang tersimpan final prefs = await SharedPreferences.getInstance(); await prefs.remove('bluetooth_device_address'); await prefs.remove('bluetooth_device_name'); if (mounted) { setState(() { _selectedDevice = null; }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Printer terputus')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal memutuskan koneksi: $e')), ); } } } /// Membuka dialog konfigurasi teks kustom Future _openCustomTextConfig() async { final result = await showDialog( context: context, builder: (context) => const CustomTextConfigDialog(), ); // Jika teks berhasil disimpan, tampilkan snackbar konfirmasi if (result == true && mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Teks kustom tersimpan!')), ); } } @override void dispose() { _urlController.dispose(); _tokenController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Konfigurasi'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Firefly III Configuration Section const Text( 'Konfigurasi Firefly III', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), const Text( 'Masukkan detail koneksi ke instance Firefly III Anda:', style: TextStyle(fontSize: 16), ), const SizedBox(height: 20), // TextField untuk URL TextFormField( controller: _urlController, decoration: const InputDecoration( labelText: 'Firefly III URL', hintText: 'https://firefly.yourdomain.com', border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.isEmpty) { return 'URL tidak boleh kosong'; } // Validasi sederhana untuk URL if (!value.startsWith('http')) { return 'URL harus dimulai dengan http:// atau https://'; } return null; }, ), const SizedBox(height: 20), // TextField untuk Token TextFormField( controller: _tokenController, decoration: const InputDecoration( labelText: 'Personal Access Token', border: OutlineInputBorder(), ), obscureText: true, // Sembunyikan token validator: (value) { if (value == null || value.isEmpty) { return 'Token tidak boleh kosong'; } return null; }, ), const SizedBox(height: 20), // Tombol Simpan ElevatedButton.icon( onPressed: _saveConfig, icon: const Icon(Icons.save), label: const Text('Simpan Konfigurasi'), ), const SizedBox(height: 10), // Tombol Test Connection ElevatedButton.icon( onPressed: _isTestingConnection ? null : _testConnection, icon: _isTestingConnection ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.link), label: Text(_isTestingConnection ? 'Menguji Koneksi...' : 'Test Koneksi'), ), const SizedBox(height: 10), // Tombol Test Authentication ElevatedButton.icon( onPressed: _isTestingAuth ? null : _testAuthentication, icon: _isTestingAuth ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.verified_user), label: Text(_isTestingAuth ? 'Menguji Autentikasi...' : 'Test Autentikasi'), ), const SizedBox(height: 30), // Bluetooth Printer Configuration Section const Text( 'Pengaturan Printer Bluetooth', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), const Text( 'Hubungkan printer thermal Anda melalui Bluetooth untuk mencetak struk.', style: TextStyle(fontSize: 16), ), const SizedBox(height: 20), // Status koneksi printer Container( padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8.0), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _connected ? 'Printer Terhubung: ${_selectedDevice?.name ?? 'Unknown'}' : 'Printer Tidak Terhubung', style: TextStyle( fontSize: 16, color: _connected ? Colors.green : Colors.red, ), ), Icon( _connected ? Icons.bluetooth_connected : Icons.bluetooth_disabled, color: _connected ? Colors.green : Colors.red, ), ], ), ), const SizedBox(height: 20), // Tombol Scan Bluetooth ElevatedButton.icon( onPressed: _isScanning ? null : _startScan, icon: _isScanning ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.bluetooth_searching), label: Text(_isScanning ? 'Memindai...' : 'Scan Perangkat'), ), const SizedBox(height: 20), // Daftar perangkat yang ditemukan if (_devices.isNotEmpty) ...[ const Text( 'Perangkat yang Ditemukan:', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _devices.length, itemBuilder: (context, index) { final device = _devices[index]; return Card( child: ListTile( title: Text(device.name ?? 'Unknown Device'), subtitle: Text(device.address ?? ''), trailing: ElevatedButton( onPressed: () => _connectToDevice(device), child: const Text('Hubungkan'), ), ), ); }, ), const SizedBox(height: 20), ], // Tombol Putuskan Koneksi if (_connected) ElevatedButton.icon( onPressed: _disconnectPrinter, icon: const Icon(Icons.bluetooth_disabled), label: const Text('Putuskan Koneksi'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), ), const SizedBox(height: 30), ], ), ), ), ); } }