cashumit/lib/screens/config_screen.dart

562 lines
17 KiB
Dart

// 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<ConfigScreen> createState() => _ConfigScreenState();
}
class _ConfigScreenState extends State<ConfigScreen> {
final _formKey = GlobalKey<FormState>();
final _urlController = TextEditingController();
final _tokenController = TextEditingController();
bool _isTestingConnection = false;
bool _isTestingAuth = false;
// Bluetooth printer variables
BluetoothPrint bluetoothPrint = BluetoothPrint.instance;
bool _isScanning = false;
List<BluetoothDevice> _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<void> _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<void> _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<void> _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<void> _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<void> _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<void> _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<void> _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<void> _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<void> _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<void> _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<void> _openCustomTextConfig() async {
final result = await showDialog<bool>(
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),
],
),
),
),
);
}
}