cashumit/lib/widgets/receipt_speed_dial.dart

241 lines
9.2 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:cashumit/services/bluetooth_service.dart';
import 'package:provider/provider.dart';
import 'package:cashumit/providers/receipt_provider.dart';
class ReceiptSpeedDial extends StatelessWidget {
final BluetoothService bluetoothService;
final Future<bool> Function() onCheckConnection;
final Future<void> Function() onPrint;
final VoidCallback onSettings;
final bool hasItems;
feat: multi-grup sumber, batch send, & perbaikan tip Multi-Grup Sumber & UI: - Model ReceiptGroup: satu struk bisa punya banyak grup sumber ( revenue) - Tombol "Tambah Sumber" dengan dashed border (max 10 grup) - Hapus label "Dari:" di SourceAccountSelector (chip saja) - Default 1 grup kosong di ReceiptProvider constructor & _resetState Akun Tujuan ke Config: - Pindah DestinationAccountSelector dari receipt body ke Config screen - Persist & load via SharedPreferences (permanen, tidak di-reset) - Dropdown auto-load dari cache/mirror saat buka Config - Tombol "Sinkronisasi Manual" sekarang simpan ke mirror & refresh dropdown Batch Send via SubmissionTask: - Model SubmissionTask: konteks per-grup (group/tip + destination + date) - TransactionSubmissionDialog refactor: terima List<SubmissionTask> - Helper buildTasksFromReceipt & buildTasksFromAllUnsubmittedReceipts - LocalReceiptsScreen: tombol "Kirim Semua Nota" + post-process status Bug Fix: - copyWith sentinel pattern: field nullable (tipSourceAccount, destinationAccount, fireflyUrl, accessToken) kini bisa di-set ke null - Tip dapat dibersihkan dengan "Batal Tip" & reset antar transaksi - Hapus tombol "Muat Akun" (redundan dgn Sinkronisasi Manual) Lainnya: - Date picker untuk tanggal transaksi - Font courier prime caching via CourierStyles - Perf: compute() untuk decode image, cancel stream bluetooth, fix rebuild loop - External link: <queries> AndroidManifest + skip canLaunchUrl - Layout cetak: hapus Ke:, sederhanakan separator grup, hapus TIP: TIDAK - Hapus TransactionLinksScreen, ganti inline copy/open di popup
2026-06-27 20:18:51 +07:00
final bool allGroupsHaveSource;
final bool hasDestination;
final Future<void> Function() onSendToFirefly;
final VoidCallback onPrintingStart;
final VoidCallback onPrintingEnd;
final Future<void> Function()? onSendAndPrint;
const ReceiptSpeedDial({
super.key,
required this.bluetoothService,
required this.onCheckConnection,
required this.onPrint,
required this.onSettings,
required this.hasItems,
feat: multi-grup sumber, batch send, & perbaikan tip Multi-Grup Sumber & UI: - Model ReceiptGroup: satu struk bisa punya banyak grup sumber ( revenue) - Tombol "Tambah Sumber" dengan dashed border (max 10 grup) - Hapus label "Dari:" di SourceAccountSelector (chip saja) - Default 1 grup kosong di ReceiptProvider constructor & _resetState Akun Tujuan ke Config: - Pindah DestinationAccountSelector dari receipt body ke Config screen - Persist & load via SharedPreferences (permanen, tidak di-reset) - Dropdown auto-load dari cache/mirror saat buka Config - Tombol "Sinkronisasi Manual" sekarang simpan ke mirror & refresh dropdown Batch Send via SubmissionTask: - Model SubmissionTask: konteks per-grup (group/tip + destination + date) - TransactionSubmissionDialog refactor: terima List<SubmissionTask> - Helper buildTasksFromReceipt & buildTasksFromAllUnsubmittedReceipts - LocalReceiptsScreen: tombol "Kirim Semua Nota" + post-process status Bug Fix: - copyWith sentinel pattern: field nullable (tipSourceAccount, destinationAccount, fireflyUrl, accessToken) kini bisa di-set ke null - Tip dapat dibersihkan dengan "Batal Tip" & reset antar transaksi - Hapus tombol "Muat Akun" (redundan dgn Sinkronisasi Manual) Lainnya: - Date picker untuk tanggal transaksi - Font courier prime caching via CourierStyles - Perf: compute() untuk decode image, cancel stream bluetooth, fix rebuild loop - External link: <queries> AndroidManifest + skip canLaunchUrl - Layout cetak: hapus Ke:, sederhanakan separator grup, hapus TIP: TIDAK - Hapus TransactionLinksScreen, ganti inline copy/open di popup
2026-06-27 20:18:51 +07:00
required this.allGroupsHaveSource,
required this.hasDestination,
required this.onSendToFirefly,
required this.onPrintingStart,
required this.onPrintingEnd,
this.onSendAndPrint,
});
feat: multi-grup sumber, batch send, & perbaikan tip Multi-Grup Sumber & UI: - Model ReceiptGroup: satu struk bisa punya banyak grup sumber ( revenue) - Tombol "Tambah Sumber" dengan dashed border (max 10 grup) - Hapus label "Dari:" di SourceAccountSelector (chip saja) - Default 1 grup kosong di ReceiptProvider constructor & _resetState Akun Tujuan ke Config: - Pindah DestinationAccountSelector dari receipt body ke Config screen - Persist & load via SharedPreferences (permanen, tidak di-reset) - Dropdown auto-load dari cache/mirror saat buka Config - Tombol "Sinkronisasi Manual" sekarang simpan ke mirror & refresh dropdown Batch Send via SubmissionTask: - Model SubmissionTask: konteks per-grup (group/tip + destination + date) - TransactionSubmissionDialog refactor: terima List<SubmissionTask> - Helper buildTasksFromReceipt & buildTasksFromAllUnsubmittedReceipts - LocalReceiptsScreen: tombol "Kirim Semua Nota" + post-process status Bug Fix: - copyWith sentinel pattern: field nullable (tipSourceAccount, destinationAccount, fireflyUrl, accessToken) kini bisa di-set ke null - Tip dapat dibersihkan dengan "Batal Tip" & reset antar transaksi - Hapus tombol "Muat Akun" (redundan dgn Sinkronisasi Manual) Lainnya: - Date picker untuk tanggal transaksi - Font courier prime caching via CourierStyles - Perf: compute() untuk decode image, cancel stream bluetooth, fix rebuild loop - External link: <queries> AndroidManifest + skip canLaunchUrl - Layout cetak: hapus Ke:, sederhanakan separator grup, hapus TIP: TIDAK - Hapus TransactionLinksScreen, ganti inline copy/open di popup
2026-06-27 20:18:51 +07:00
bool get _canSubmit =>
hasItems && allGroupsHaveSource && hasDestination;
@override
Widget build(BuildContext context) {
final primaryColor = Theme.of(context).colorScheme.primary;
return SpeedDial(
icon: Icons.menu,
activeIcon: Icons.close,
spacing: 6,
spaceBetweenChildren: 8,
backgroundColor: primaryColor,
foregroundColor: Colors.white,
activeBackgroundColor: Colors.red.shade600,
activeForegroundColor: Colors.white,
elevation: 4,
children: [
if (onSendAndPrint != null)
SpeedDialChild(
child: const Icon(Icons.done_all, color: Colors.white),
label: 'Kirim & Cetak Sekaligus',
labelStyle: const TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF004D40),
),
labelBackgroundColor: const Color(0xFFE0F2F1),
backgroundColor: const Color(0xFF00897B),
onTap: _canSubmit
? onSendAndPrint
: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(!hasDestination
? 'Pilih akun tujuan terlebih dahulu'
: !allGroupsHaveSource
? 'Setiap grup harus punya akun sumber'
: 'Tambahkan item terlebih dahulu'),
duration: const Duration(seconds: 2),
),
);
},
),
SpeedDialChild(
child: const Icon(Icons.send_rounded, color: Colors.white),
label: 'Kirim ke Firefly',
labelStyle: const TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF0D47A1),
),
labelBackgroundColor: const Color(0xFFE3F2FD),
backgroundColor: const Color(0xFF1E88E5),
feat: multi-grup sumber, batch send, & perbaikan tip Multi-Grup Sumber & UI: - Model ReceiptGroup: satu struk bisa punya banyak grup sumber ( revenue) - Tombol "Tambah Sumber" dengan dashed border (max 10 grup) - Hapus label "Dari:" di SourceAccountSelector (chip saja) - Default 1 grup kosong di ReceiptProvider constructor & _resetState Akun Tujuan ke Config: - Pindah DestinationAccountSelector dari receipt body ke Config screen - Persist & load via SharedPreferences (permanen, tidak di-reset) - Dropdown auto-load dari cache/mirror saat buka Config - Tombol "Sinkronisasi Manual" sekarang simpan ke mirror & refresh dropdown Batch Send via SubmissionTask: - Model SubmissionTask: konteks per-grup (group/tip + destination + date) - TransactionSubmissionDialog refactor: terima List<SubmissionTask> - Helper buildTasksFromReceipt & buildTasksFromAllUnsubmittedReceipts - LocalReceiptsScreen: tombol "Kirim Semua Nota" + post-process status Bug Fix: - copyWith sentinel pattern: field nullable (tipSourceAccount, destinationAccount, fireflyUrl, accessToken) kini bisa di-set ke null - Tip dapat dibersihkan dengan "Batal Tip" & reset antar transaksi - Hapus tombol "Muat Akun" (redundan dgn Sinkronisasi Manual) Lainnya: - Date picker untuk tanggal transaksi - Font courier prime caching via CourierStyles - Perf: compute() untuk decode image, cancel stream bluetooth, fix rebuild loop - External link: <queries> AndroidManifest + skip canLaunchUrl - Layout cetak: hapus Ke:, sederhanakan separator grup, hapus TIP: TIDAK - Hapus TransactionLinksScreen, ganti inline copy/open di popup
2026-06-27 20:18:51 +07:00
onTap: _canSubmit
? onSendToFirefly
: () {
ScaffoldMessenger.of(context).showSnackBar(
feat: multi-grup sumber, batch send, & perbaikan tip Multi-Grup Sumber & UI: - Model ReceiptGroup: satu struk bisa punya banyak grup sumber ( revenue) - Tombol "Tambah Sumber" dengan dashed border (max 10 grup) - Hapus label "Dari:" di SourceAccountSelector (chip saja) - Default 1 grup kosong di ReceiptProvider constructor & _resetState Akun Tujuan ke Config: - Pindah DestinationAccountSelector dari receipt body ke Config screen - Persist & load via SharedPreferences (permanen, tidak di-reset) - Dropdown auto-load dari cache/mirror saat buka Config - Tombol "Sinkronisasi Manual" sekarang simpan ke mirror & refresh dropdown Batch Send via SubmissionTask: - Model SubmissionTask: konteks per-grup (group/tip + destination + date) - TransactionSubmissionDialog refactor: terima List<SubmissionTask> - Helper buildTasksFromReceipt & buildTasksFromAllUnsubmittedReceipts - LocalReceiptsScreen: tombol "Kirim Semua Nota" + post-process status Bug Fix: - copyWith sentinel pattern: field nullable (tipSourceAccount, destinationAccount, fireflyUrl, accessToken) kini bisa di-set ke null - Tip dapat dibersihkan dengan "Batal Tip" & reset antar transaksi - Hapus tombol "Muat Akun" (redundan dgn Sinkronisasi Manual) Lainnya: - Date picker untuk tanggal transaksi - Font courier prime caching via CourierStyles - Perf: compute() untuk decode image, cancel stream bluetooth, fix rebuild loop - External link: <queries> AndroidManifest + skip canLaunchUrl - Layout cetak: hapus Ke:, sederhanakan separator grup, hapus TIP: TIDAK - Hapus TransactionLinksScreen, ganti inline copy/open di popup
2026-06-27 20:18:51 +07:00
SnackBar(
content: Text(!hasDestination
? 'Pilih akun tujuan terlebih dahulu'
: !allGroupsHaveSource
? 'Setiap grup harus punya akun sumber'
: 'Tambahkan item terlebih dahulu'),
duration: const Duration(seconds: 2),
),
);
},
),
SpeedDialChild(
child: bluetoothService.isPrinting
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
color: Colors.white, strokeWidth: 2),
)
: const Icon(Icons.print_rounded, color: Colors.white),
label: 'Cetak Struk Saja',
labelStyle: const TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF4A148C),
),
labelBackgroundColor: const Color(0xFFEDE7F6),
backgroundColor: const Color(0xFF7E57C2),
onTap: bluetoothService.isPrinting
? null
: () async {
onPrintingStart();
2025-09-07 22:29:09 +07:00
try {
final isConnected = await onCheckConnection();
if (isConnected) {
await onPrint();
} else {
if (bluetoothService.connectedDevice != null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
2025-09-07 22:29:09 +07:00
content:
Text('Mencoba menyambungkan ke printer...')),
);
try {
2025-09-07 22:29:09 +07:00
bool connectResult =
await bluetoothService.connectToDevice(
bluetoothService.connectedDevice!);
if (!connectResult) {
throw Exception('Gagal menyambungkan ke printer');
}
2025-09-07 22:29:09 +07:00
await Future.delayed(
const Duration(milliseconds: 500));
final isConnectedAfterConnect =
await onCheckConnection();
if (isConnectedAfterConnect) {
await onPrint();
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
2025-09-07 22:29:09 +07:00
content:
Text('Gagal menyambungkan ke printer')),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
2025-09-07 22:29:09 +07:00
content:
Text('Gagal menyambungkan ke printer: $e')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
2025-09-07 22:29:09 +07:00
content:
Text('Hubungkan printer terlebih dahulu')),
);
}
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
2025-09-07 22:29:09 +07:00
SnackBar(content: Text('Terjadi kesalahan: $e')),
);
} finally {
onPrintingEnd();
}
},
),
SpeedDialChild(
child: const Icon(Icons.bookmark_added_rounded, color: Colors.white),
label: 'Simpan Nanti (Offline)',
labelStyle: const TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFFE65100),
),
labelBackgroundColor: const Color(0xFFFFF3E0),
backgroundColor: const Color(0xFFEF6C00),
onTap: _canSubmit
? () async {
final receiptProvider = context.read<ReceiptProvider>();
try {
final receiptId =
await receiptProvider.saveTransactionLocally();
if (receiptId != null) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Nota berhasil disimpan untuk dikirim nanti'),
),
);
}
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Gagal menyimpan nota: $e'),
),
);
}
}
}
: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(!hasDestination
? 'Pilih akun tujuan terlebih dahulu'
: !allGroupsHaveSource
? 'Setiap grup harus punya akun sumber'
: 'Tambahkan item terlebih dahulu'),
duration: const Duration(seconds: 2),
),
);
},
),
SpeedDialChild(
child: const Icon(Icons.settings_rounded, color: Colors.white),
label: 'Pengaturan Server',
labelStyle: const TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF263238),
),
labelBackgroundColor: const Color(0xFFECEFF1),
backgroundColor: const Color(0xFF546E7A),
onTap: onSettings,
),
],
);
}
2025-09-07 22:29:09 +07:00
}