520 lines
16 KiB
Dart
520 lines
16 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:cashumit/services/firefly_api_service.dart';
|
|
import 'package:bluetooth_print/bluetooth_print.dart';
|
|
import 'package:bluetooth_print/bluetooth_print_model.dart';
|
|
import 'package:cashumit/widgets/custom_text_config_dialog.dart'; // Tambahkan import ini
|
|
|
|
class SettingsScreen extends StatefulWidget {
|
|
const SettingsScreen({super.key});
|
|
|
|
@override
|
|
State<SettingsScreen> createState() => _SettingsScreenState();
|
|
}
|
|
|
|
class _SettingsScreenState extends State<SettingsScreen> {
|
|
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;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadSettings();
|
|
_initBluetooth();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_urlController.dispose();
|
|
_tokenController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadSettings() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
// Tambahkan pengecekan mounted sebelum setState
|
|
if (mounted) {
|
|
setState(() {
|
|
_urlController.text = prefs.getString('firefly_url') ?? '';
|
|
_tokenController.text = prefs.getString('firefly_token') ?? '';
|
|
});
|
|
}
|
|
}
|
|
|
|
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 ?? '');
|
|
}
|
|
|
|
Future<void> _saveSettings() async {
|
|
if (_formKey.currentState!.validate()) {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString('firefly_url', _urlController.text.trim());
|
|
await prefs.setString('firefly_token', _tokenController.text.trim());
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Pengaturan berhasil disimpan')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _testConnection() async {
|
|
// Tambahkan pengecekan mounted
|
|
if (!mounted) return;
|
|
|
|
setState(() {
|
|
_isTestingConnection = true;
|
|
});
|
|
|
|
// Simpan pengaturan terlebih dahulu
|
|
await _saveSettings();
|
|
|
|
// Uji koneksi
|
|
final success = await FireflyApiService.testConnection(baseUrl: _urlController.text.trim());
|
|
|
|
// Tambahkan pengecekan mounted sebelum setState
|
|
if (mounted) {
|
|
setState(() {
|
|
_isTestingConnection = false;
|
|
});
|
|
|
|
if (success) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Koneksi berhasil!')),
|
|
);
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Koneksi gagal. Periksa URL dan pastikan Firefly III berjalan.')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _testAuthentication() async {
|
|
// Tambahkan pengecekan mounted
|
|
if (!mounted) return;
|
|
|
|
setState(() {
|
|
_isTestingAuth = true;
|
|
});
|
|
|
|
// Simpan pengaturan terlebih dahulu
|
|
await _saveSettings();
|
|
|
|
// Uji autentikasi
|
|
final success = await FireflyApiService.testAuthentication(baseUrl: _urlController.text.trim(), accessToken: _tokenController.text.trim());
|
|
|
|
// Tambahkan pengecekan mounted sebelum setState
|
|
if (mounted) {
|
|
setState(() {
|
|
_isTestingAuth = false;
|
|
});
|
|
|
|
if (success) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Autentikasi berhasil!')),
|
|
);
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Autentikasi gagal. Periksa token yang digunakan.')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _scanDevices() async {
|
|
setState(() {
|
|
_isScanning = true;
|
|
_devices = [];
|
|
});
|
|
|
|
try {
|
|
// Mulai scan perangkat Bluetooth
|
|
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;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _connectToDevice(BluetoothDevice device) async {
|
|
try {
|
|
await bluetoothPrint.connect(device);
|
|
setState(() {
|
|
_selectedDevice = device;
|
|
});
|
|
|
|
// Simpan device yang dipilih
|
|
await _saveBluetoothDevice(device);
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Berhasil terhubung ke printer')),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Gagal terhubung ke printer: $e')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Memutuskan koneksi dari printer bluetooth
|
|
Future<void> _disconnect() async {
|
|
try {
|
|
await bluetoothPrint.disconnect();
|
|
setState(() {
|
|
_connected = false;
|
|
_selectedDevice = null;
|
|
});
|
|
|
|
// Hapus device yang tersimpan
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove('bluetooth_device_address');
|
|
await prefs.remove('bluetooth_device_name');
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Berhasil memutus koneksi dari printer')),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Gagal memutus koneksi dari printer: $e')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Membuka dialog konfigurasi teks kustom
|
|
Future<void> _openCustomTextConfig() async {
|
|
final result = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => const CustomTextConfigDialog(), // Tambahkan import di bagian atas
|
|
);
|
|
|
|
// Jika teks kustom berhasil disimpan, tampilkan snackbar
|
|
if (result == true && mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Teks kustom berhasil disimpan')),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Pengaturan'),
|
|
centerTitle: true,
|
|
),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// === Firefly III Settings ===
|
|
const Text(
|
|
'Koneksi ke Firefly III',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Masukkan URL lengkap ke instance Firefly III Anda (contoh: https://firefly.example.com)',
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextFormField(
|
|
controller: _urlController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Firefly III URL',
|
|
border: OutlineInputBorder(),
|
|
hintText: 'https://firefly.example.com',
|
|
),
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Mohon masukkan URL Firefly III';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Masukkan Personal Access Token dari Firefly III',
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextFormField(
|
|
controller: _tokenController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Access Token',
|
|
border: OutlineInputBorder(),
|
|
),
|
|
obscureText: true,
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Mohon masukkan access token';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: _isTestingConnection ? null : _testConnection,
|
|
child: _isTestingConnection
|
|
? const Text('Menguji Koneksi...')
|
|
: const Text('Uji Koneksi'),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: _isTestingAuth ? null : _testAuthentication,
|
|
child: _isTestingAuth
|
|
? const Text('Menguji Auth...')
|
|
: const Text('Uji Auth'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: _saveSettings,
|
|
child: const Text('Simpan Pengaturan Firefly III'),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
// === Printer Settings ===
|
|
const Text(
|
|
'Pengaturan Printer Bluetooth',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Hubungkan printer thermal Anda melalui Bluetooth untuk mencetak struk.',
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Status koneksi
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.grey),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
const Text('Status Printer:'),
|
|
Text(
|
|
_connected ? 'Terhubung' : 'Terputus',
|
|
style: TextStyle(
|
|
color: _connected ? Colors.green : Colors.red,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Tombol scan
|
|
ElevatedButton.icon(
|
|
onPressed: _isScanning ? null : _scanDevices,
|
|
icon: _isScanning
|
|
? const SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: const Icon(Icons.bluetooth_searching),
|
|
label: Text(_isScanning ? 'Memindai...' : 'Cari Printer'),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Daftar perangkat
|
|
SizedBox(
|
|
height: 200, // Tentukan tinggi tetap untuk daftar
|
|
child: _devices.isEmpty
|
|
? const Center(
|
|
child: Text('Tidak ada perangkat ditemukan'),
|
|
)
|
|
: ListView.builder(
|
|
itemCount: _devices.length,
|
|
itemBuilder: (context, index) {
|
|
final device = _devices[index];
|
|
final isSelected = _selectedDevice?.address == device.address;
|
|
|
|
return Card(
|
|
child: ListTile(
|
|
title: Text(device.name ?? 'Unknown Device'),
|
|
subtitle: Text(device.address ?? ''),
|
|
trailing: isSelected
|
|
? const Icon(Icons.check, color: Colors.green)
|
|
: null,
|
|
onTap: () => _connectToDevice(device),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
// Tombol disconnect
|
|
if (_connected)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 16),
|
|
child: ElevatedButton.icon(
|
|
onPressed: _disconnect,
|
|
icon: const Icon(Icons.bluetooth_disabled),
|
|
label: const Text('Putus Koneksi'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red,
|
|
),
|
|
),
|
|
),
|
|
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
// Fungsi tambahan jika diperlukan
|
|
},
|
|
child: const Text('Simpan Pengaturan Printer'),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
// === Custom Text Settings ===
|
|
const Text(
|
|
'Teks Kustom Struk',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Sesuaikan teks disclaimer, ucapan terima kasih, dan pantun di struk Anda.',
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: _openCustomTextConfig,
|
|
child: const Text('Edit Teks Kustom'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |