printer receipt

This commit is contained in:
efrilm
2025-11-04 22:13:15 +07:00
parent a87b62ca9f
commit 1b24bae23a
32 changed files with 4177 additions and 75 deletions
+6
View File
@@ -9,7 +9,10 @@ import '../application/order/order_form/order_form_bloc.dart';
import '../application/order/order_loader/order_loader_bloc.dart';
import '../application/outlet/outlet_loader/outlet_loader_bloc.dart';
import '../application/payment_method/payment_method_loader/payment_method_loader_bloc.dart';
import '../application/printer/bluetooth/bluetooth_connect/bluetooth_connect_bloc.dart';
import '../application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.dart';
import '../application/printer/printer_form/printer_form_bloc.dart';
import '../application/printer/printer_loader/printer_loader_bloc.dart';
import '../application/product/product_loader/product_loader_bloc.dart';
import '../application/table/table_form/table_form_bloc.dart';
import '../application/table/table_loader/table_loader_bloc.dart';
@@ -47,6 +50,9 @@ class _AppWidgetState extends State<AppWidget> {
BlocProvider(create: (context) => getIt<CustomerLoaderBloc>()),
BlocProvider(create: (context) => getIt<VoidFormBloc>()),
BlocProvider(create: (context) => getIt<BluetoothLoaderBloc>()),
BlocProvider(create: (context) => getIt<BluetoothConnectBloc>()),
BlocProvider(create: (context) => getIt<PrinterFormBloc>()),
BlocProvider(create: (context) => getIt<PrinterLoaderBloc>()),
],
child: MaterialApp.router(
debugShowCheckedModeBanner: false,
@@ -4,8 +4,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/checkout/checkout_form/checkout_form_bloc.dart';
import '../../../application/outlet/outlet_loader/outlet_loader_bloc.dart';
import '../../../application/printer/bluetooth/bluetooth_connect/bluetooth_connect_bloc.dart';
import '../../../application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.dart';
import '../../../common/extension/extension.dart';
import '../../../common/function/app_function.dart';
import '../../../common/theme/theme.dart';
import '../../../common/types/order_type.dart';
import '../../../domain/delivery/delivery.dart';
@@ -12,6 +12,7 @@ class PrinterBluetoothDialog extends StatefulWidget {
class _PrinterBluetoothDialogState extends State<PrinterBluetoothDialog> {
@override
void initState() {
loadPermissionBluetooth();
context.read<BluetoothLoaderBloc>().add(BluetoothLoaderEvent.fetched());
super.initState();
}
@@ -38,33 +39,42 @@ class _PrinterBluetoothDialogState extends State<PrinterBluetoothDialog> {
mainAxisAlignment: MainAxisAlignment.start,
children: state.bluetoothDevices
.map(
(item) => Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: AppColor.border, width: 1),
(item) => GestureDetector(
onTap: () {
context.read<BluetoothConnectBloc>().add(
BluetoothConnectEvent.connect(item.macAdress),
);
widget.onSelected(item.macAdress);
context.router.maybePop();
},
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: AppColor.border, width: 1),
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.name,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.w600,
color: AppColor.primary,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
item.name,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.w600,
color: AppColor.primary,
),
),
),
SpaceHeight(4),
Text(
item.macAdress,
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
SpaceHeight(4),
Text(
item.macAdress,
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
),
),
),
],
],
),
),
),
)
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
import '../../../domain/auth/auth.dart';
import '../../../domain/order/order.dart';
import '../../../domain/printer/printer.dart';
import '../../../domain/table/table.dart';
class AppFlushbar {
@@ -80,4 +81,18 @@ class AppFlushbar {
localStorageError: (value) => 'Terjadi kesalahan, silahkan coba lagi',
),
);
static void showPrinterFailureToast(
BuildContext context,
PrinterFailure failure,
) => showError(
context,
failure.map(
serverError: (value) => value.failure.toStringFormatted(context),
dynamicErrorMessage: (value) => value.erroMessage,
unexpectedError: (value) => 'Terjadi kesalahan, silahkan coba lagi',
empty: (value) => 'Tidak ada data',
localStorageError: (value) => 'Terjadi kesalahan, silahkan coba lagi',
),
);
}
@@ -1,58 +1,223 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../../../application/printer/printer_form/printer_form_bloc.dart';
import '../../../../../../../common/data/printer_data.dart';
import '../../../../../../../common/extension/extension.dart';
import '../../../../../../../common/theme/theme.dart';
import '../../../../../../../domain/printer/printer.dart';
import '../../../../../../components/button/button.dart';
import '../../../../../../components/dialog/dialog.dart';
import '../../../../../../components/field/field.dart';
import '../../../../../../components/spaces/space.dart';
import '../../../../../../components/toast/flushbar.dart';
class SettingPrinterForm extends StatelessWidget {
class SettingPrinterForm extends StatefulWidget {
final String code;
const SettingPrinterForm({super.key, required this.code});
final Printer? printer;
final Function()? onCancel;
final Function()? onSuccess;
const SettingPrinterForm({
super.key,
required this.code,
this.printer,
this.onCancel,
this.onSuccess,
});
@override
State<SettingPrinterForm> createState() => _SettingPrinterFormState();
}
class _SettingPrinterFormState extends State<SettingPrinterForm> {
final TextEditingController _nameController = TextEditingController();
void setup() {
if (widget.printer != null) {
context.read<PrinterFormBloc>().add(
PrinterFormEvent.typeChanged(widget.printer!.type),
);
context.read<PrinterFormBloc>().add(
PrinterFormEvent.addressChanged(widget.printer!.address),
);
context.read<PrinterFormBloc>().add(
PrinterFormEvent.codeChanged(widget.printer!.code),
);
context.read<PrinterFormBloc>().add(
PrinterFormEvent.nameChanged(widget.printer!.name),
);
context.read<PrinterFormBloc>().add(
PrinterFormEvent.paperChanged(widget.printer!.paper),
);
_nameController.text = widget.printer!.name;
}
}
@override
void initState() {
setup();
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(16.0),
),
child: Column(
children: [
AppDropdownSearch<String>(
label: 'Tipe',
hintText: 'Printer Tipe',
items: printerTypes,
itemAsString: (value) => value,
),
SpaceHeight(12),
SizedBox(
width: double.infinity,
child: AppElevatedButton.outlined(
onPressed: () {
showDialog(
context: context,
builder: (context) =>
PrinterBluetoothDialog(onSelected: (value) {}),
);
},
label: 'Cari',
return MultiBlocListener(
listeners: [
BlocListener<PrinterFormBloc, PrinterFormState>(
listenWhen: (p, c) =>
p.failureOrCreateSuccess != c.failureOrCreateSuccess,
listener: (context, state) {
state.failureOrCreateSuccess.fold(
() => null,
(either) => either.fold(
(f) => AppFlushbar.showPrinterFailureToast(context, f),
(either) {
widget.onSuccess?.call();
AppFlushbar.showSuccess(
context,
'Printer ${widget.code.toTitleCase()} berhasil ditambahkan',
);
},
),
);
},
),
BlocListener<PrinterFormBloc, PrinterFormState>(
listenWhen: (p, c) =>
p.failureOrUpdateSuccess != c.failureOrUpdateSuccess,
listener: (context, state) {
state.failureOrUpdateSuccess.fold(
() => null,
(either) => either.fold(
(f) => AppFlushbar.showPrinterFailureToast(context, f),
(either) {
widget.onSuccess?.call();
AppFlushbar.showSuccess(
context,
'Printer ${widget.code.toTitleCase()} berhasil diubah',
);
},
),
);
},
),
],
child: BlocBuilder<PrinterFormBloc, PrinterFormState>(
builder: (context, state) {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(16.0),
),
),
SpaceHeight(12),
AppTextFormField(label: 'Nama Printer'),
SpaceHeight(12),
AppDropdownSearch<String>(
label: 'Kertas',
hintText: 'Kertas Tipe',
items: paperTypes,
itemAsString: (value) => "$value mm",
),
SpaceHeight(20),
AppElevatedButton.filled(onPressed: () {}, label: 'Simpan'),
],
child: Column(
children: [
AppDropdownSearch<String>(
label: 'Tipe',
hintText: 'Printer Tipe',
items: printerTypes,
itemAsString: (value) => value,
selectedItem: state.type,
onChanged: (value) {
context.read<PrinterFormBloc>().add(
PrinterFormEvent.typeChanged(value!),
);
},
),
SpaceHeight(12),
state.type == 'Bluetooth'
? SizedBox(
width: double.infinity,
child: AppElevatedButton.outlined(
onPressed: () {
showDialog(
context: context,
builder: (context) => PrinterBluetoothDialog(
onSelected: (value) {
context.read<PrinterFormBloc>().add(
PrinterFormEvent.addressChanged(value),
);
},
),
);
},
label: state.address != '' ? state.address : 'Cari',
),
)
: AppTextFormField(
label: 'Network',
onChanged: (value) {
context.read<PrinterFormBloc>().add(
PrinterFormEvent.addressChanged(value),
);
},
),
SpaceHeight(12),
AppTextFormField(
label: 'Nama Printer',
controller: _nameController,
onChanged: (value) {
context.read<PrinterFormBloc>().add(
PrinterFormEvent.nameChanged(value),
);
},
),
SpaceHeight(12),
AppDropdownSearch<String>(
label: 'Kertas',
hintText: 'Kertas Tipe',
items: paperTypes,
itemAsString: (value) => "$value mm",
selectedItem: state.paper,
onChanged: (value) {
context.read<PrinterFormBloc>().add(
PrinterFormEvent.paperChanged(value!),
);
},
),
SpaceHeight(20),
Row(
children: [
if (widget.printer != null) ...[
Expanded(
child: AppElevatedButton.outlined(
onPressed: widget.onCancel,
label: 'Batal',
),
),
SpaceWidth(12),
],
Expanded(
child: AppElevatedButton.filled(
onPressed: () {
if (widget.printer == null) {
if (!state.isCreating) {
context.read<PrinterFormBloc>().add(
PrinterFormEvent.codeChanged(widget.code),
);
context.read<PrinterFormBloc>().add(
PrinterFormEvent.created(),
);
}
} else {
context.read<PrinterFormBloc>().add(
PrinterFormEvent.updated(widget.printer!.id),
);
}
},
label: widget.printer == null ? 'Simpan' : 'Ubah',
isLoading: widget.printer == null
? state.isCreating
: state.isUpdate,
),
),
],
),
],
),
);
},
),
);
}
@@ -1,18 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../../../application/printer/printer_loader/printer_loader_bloc.dart';
import '../../../../../../../common/theme/theme.dart';
import '../../../../../../components/loader/loader_with_text.dart';
import '../../widgets/printer_card.dart';
import 'setting_printer_form.dart';
class SettingPrinterReceipt extends StatelessWidget {
class SettingPrinterReceipt extends StatefulWidget {
const SettingPrinterReceipt({super.key});
@override
State<SettingPrinterReceipt> createState() => _SettingPrinterReceiptState();
}
class _SettingPrinterReceiptState extends State<SettingPrinterReceipt> {
bool isEdit = false;
@override
initState() {
context.read<PrinterLoaderBloc>().add(
const PrinterLoaderEvent.getByCode('receipt'),
);
super.initState();
}
@override
Widget build(BuildContext context) {
return Material(
color: AppColor.background,
child: Padding(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(children: [SettingPrinterForm(code: 'receipt')]),
child: Column(
children: [
BlocBuilder<PrinterLoaderBloc, PrinterLoaderState>(
builder: (context, state) {
if (state.isFetching) {
return const Center(child: LoaderWithText());
}
if (state.printer.code == '') {
return SettingPrinterForm(
code: 'receipt',
onSuccess: () {
context.read<PrinterLoaderBloc>().add(
PrinterLoaderEvent.getByCode('receipt'),
);
},
);
}
return isEdit
? SettingPrinterForm(
code: 'receipt',
printer: state.printer,
onCancel: () {
setState(() {
isEdit = false;
});
},
onSuccess: () {
context.read<PrinterLoaderBloc>().add(
PrinterLoaderEvent.getByCode('receipt'),
);
setState(() {
isEdit = false;
});
},
)
: PrinterCard(
printer: state.printer,
onEdit: () {
setState(() {
isEdit = true;
});
},
);
},
),
],
),
),
);
}
@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import '../../../../../../common/theme/theme.dart';
import '../../../../../../domain/printer/printer.dart';
import '../../../../../components/spaces/space.dart';
class PrinterCard extends StatelessWidget {
final Printer printer;
final VoidCallback? onEdit;
final VoidCallback? onDelete;
final VoidCallback? onTestPrint;
const PrinterCard({
super.key,
required this.printer,
this.onEdit,
this.onDelete,
this.onTestPrint,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
color: AppColor.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Printer icon
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColor.disabled.withOpacity(0.3),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(Icons.print_outlined),
),
const SpaceWidth(12),
// Printer info
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
printer.name,
style: AppStyle.lg.copyWith(
fontWeight: FontWeight.w600,
color: AppColor.primary,
),
),
const SpaceHeight(4),
Text(
"${printer.paper} mm | ${printer.type}",
style: AppStyle.sm,
),
const SpaceHeight(2),
Text(
printer.address,
style: AppStyle.sm.copyWith(color: AppColor.textSecondary),
),
],
),
),
// Popup menu
PopupMenuButton<String>(
color: AppColor.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
onSelected: (value) {
switch (value) {
case 'edit':
onEdit?.call();
break;
case 'delete':
onDelete?.call();
break;
case 'test':
onTestPrint?.call();
break;
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Text('Edit', style: AppStyle.md),
),
PopupMenuItem(
value: 'delete',
child: Text('Hapus', style: AppStyle.md),
),
PopupMenuItem(
value: 'test',
child: Text('Test Print', style: AppStyle.md),
),
],
),
],
),
);
}
}