first commit

This commit is contained in:
Aditya Siregar
2025-07-30 22:38:44 +07:00
commit 73320561b0
444 changed files with 64633 additions and 0 deletions
@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
import '../../../core/constants/colors.dart';
import '../../setting/bloc/discount/discount_bloc.dart';
class DiscountDialog extends StatefulWidget {
const DiscountDialog({super.key});
@override
State<DiscountDialog> createState() => _DiscountDialogState();
}
class _DiscountDialogState extends State<DiscountDialog> {
@override
void initState() {
context.read<DiscountBloc>().add(const DiscountEvent.getDiscounts());
super.initState();
}
int discountIdSelected = 0;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Stack(
alignment: Alignment.center,
children: [
const Text(
'DISKON',
style: TextStyle(
color: AppColors.primary,
fontSize: 28,
fontWeight: FontWeight.w600,
),
),
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () {
context.pop();
},
icon: const Icon(
Icons.cancel,
color: AppColors.primary,
size: 30.0,
),
),
),
],
),
content: BlocBuilder<DiscountBloc, DiscountState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const SizedBox.shrink(),
loading: () => const Center(
child: CircularProgressIndicator(),
),
loaded: (discounts) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: discounts
.map(
(discount) => ListTile(
title: Text('Nama Diskon: ${discount.name}'),
subtitle: Text('Potongan harga (${discount.value}%)'),
contentPadding: EdgeInsets.zero,
textColor: AppColors.primary,
trailing: Checkbox(
value: discount.id == discountIdSelected,
onChanged: (value) {
setState(() {
discountIdSelected = discount.id!;
context.read<CheckoutBloc>().add(
CheckoutEvent.addDiscount(
discount,
),
);
});
},
),
onTap: () {
// context.pop();
},
),
)
.toList(),
);
},
);
},
),
);
}
}
@@ -0,0 +1,282 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/home/models/order_type.dart';
import 'package:intl/intl.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/home/bloc/qris/qris_bloc.dart';
import 'package:enaklo_pos/presentation/home/widgets/success_payment_dialog.dart';
import 'package:enaklo_pos/presentation/setting/bloc/sync_order/sync_order_bloc.dart';
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
import 'package:widgets_to_image/widgets_to_image.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart';
import '../bloc/order/order_bloc.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
class PaymentQrisDialog extends StatefulWidget {
final List<ProductQuantity> items;
final int discount;
final int discountAmount;
final int tax;
final int serviceCharge;
final int paymentAmount;
final String customerName;
final int tableNumber;
final String status;
final String paymentStatus;
final String paymentMethod;
final int price;
final int totalQty;
final int subTotal;
final bool? isTablePaymentPage;
const PaymentQrisDialog({
super.key,
required this.items,
required this.discount,
required this.discountAmount,
required this.tax,
required this.serviceCharge,
required this.paymentAmount,
required this.customerName,
required this.tableNumber,
required this.status,
required this.paymentStatus,
required this.paymentMethod,
required this.price,
required this.totalQty,
required this.subTotal,
this.isTablePaymentPage = false,
});
@override
State<PaymentQrisDialog> createState() => _PaymentQrisDialogState();
}
class _PaymentQrisDialogState extends State<PaymentQrisDialog> {
String orderId = '';
Timer? timer;
WidgetsToImageController controller = WidgetsToImageController();
@override
void initState() {
orderId = DateTime.now().millisecondsSinceEpoch.toString();
context.read<QrisBloc>().add(QrisEvent.generateQRCode(
orderId,
widget.price,
));
super.initState();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
scrollable: true,
contentPadding: const EdgeInsets.all(0),
backgroundColor: AppColors.primary,
content: SizedBox(
width: context.deviceWidth * 0.4,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'Pembayaran QRIS',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontFamily: 'Quicksand',
fontWeight: FontWeight.w700,
height: 0,
),
),
),
const SpaceHeight(6.0),
Container(
width: context.deviceWidth,
padding: const EdgeInsets.all(14.0),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
color: AppColors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
BlocListener<QrisBloc, QrisState>(
listener: (context, state) {
state.maybeWhen(orElse: () {
return;
}, qrisResponse: (data) {
const onSec = Duration(seconds: 5);
timer = Timer.periodic(onSec, (timer) {
context
.read<QrisBloc>()
.add(QrisEvent.checkPaymentStatus(
orderId.toString(),
));
});
}, success: (message) async {
context.read<OrderBloc>().add(OrderEvent.order(
widget.items,
widget.discount,
widget.discountAmount,
widget.tax,
widget.serviceCharge,
widget.paymentAmount,
widget.customerName,
widget.tableNumber,
'completed',
'paid',
'Qris',
widget.price,
OrderType.dineIn));
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) => SuccessPaymentDialog(
isTablePaymentPage: widget.isTablePaymentPage,
data: widget.items,
totalQty: widget.totalQty,
totalPrice: widget.price,
totalTax: widget.tax,
totalDiscount: widget.discountAmount,
subTotal: widget.subTotal,
normalPrice: widget.price,
totalService: widget.serviceCharge,
draftName: widget.customerName,
),
);
});
},
child: BlocBuilder<QrisBloc, QrisState>(
builder: (context, state) {
log('state: $state');
return state.maybeWhen(
orElse: () {
return const SizedBox();
},
qrisResponse: (data) {
log("URL: ${data.actions!.first.url!}");
return WidgetsToImage(
controller: controller,
child: Container(
width: 340.0,
// height: 256.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.white,
),
child: Column(
children: [
Center(
child: Image.network(
data.actions!.first.url!,
),
),
// const SpaceHeight(5.0),
Text(
NumberFormat.currency(
locale: 'id',
symbol: 'Rp ',
).format(widget.price),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold),
),
],
),
),
);
},
loading: () {
return Container(
width: 256.0,
height: 256.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Colors.white,
),
child: const Center(
child: CircularProgressIndicator(
color: AppColors.white,
),
),
);
},
);
},
),
),
const SpaceHeight(12.0),
const Text(
'Scan QRIS to make payment',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
),
),
const SpaceHeight(12.0),
//button print qr
ElevatedButton(
onPressed: () async {
try {
final sizeReceipt = await AuthLocalDataSource().getSizeReceipt();
final bytes = await controller.capture();
final printValue = await PrintDataoutputs.instance
.printQRIS(widget.price, bytes!, int.parse(sizeReceipt));
// Get the receipt printer to print QRIS
final receiptPrinter = await ProductLocalDatasource.instance
.getPrinterByCode('receipt');
if (receiptPrinter != null) {
await PrinterService().printWithPrinter(
receiptPrinter,
printValue,
context
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('No receipt printer configured')),
);
}
} catch (e) {
log("Error printing QRIS: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing QRIS: $e')),
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
child: const Text(
'Print QRIS',
style: TextStyle(
color: Colors.white,
fontSize: 16,
),
),
),
],
),
),
],
),
),
);
}
}
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
import '../../../core/constants/colors.dart';
class ServiceDialog extends StatelessWidget {
const ServiceDialog({super.key});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Stack(
alignment: Alignment.center,
children: [
const Text(
'LAYANAN',
style: TextStyle(
color: AppColors.primary,
fontSize: 28,
fontWeight: FontWeight.w600,
),
),
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () => context.pop(),
icon: const Icon(
Icons.cancel,
color: AppColors.primary,
size: 30.0,
),
),
),
],
),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
initial: () => const SizedBox(),
loading: () => const Center(child: CircularProgressIndicator()),
loaded: (data, a, b, c, d, service, e, f, g, orderType) => ListTile(
title: Text('Presentase ($service%)'),
subtitle: const Text('Biaya layanan'),
contentPadding: EdgeInsets.zero,
textColor: AppColors.primary,
trailing: Checkbox(
value: service > 0,
onChanged: (value) {
context.read<CheckoutBloc>().add(
CheckoutEvent.addServiceCharge(service > 0 ? 0 : service),
);
},
),
onTap: () {
context.pop();
},
),
orElse: () => const SizedBox(),
);
},
),
],
),
);
}
}
@@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import '../../../core/constants/colors.dart';
import '../bloc/checkout/checkout_bloc.dart';
class TaxDialog extends StatelessWidget {
const TaxDialog({super.key});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Stack(
alignment: Alignment.center,
children: [
const Text(
'PAJAK PB1',
style: TextStyle(
color: AppColors.primary,
fontSize: 28,
fontWeight: FontWeight.w600,
),
),
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () => context.pop(),
icon: const Icon(
Icons.cancel,
color: AppColors.primary,
size: 30.0,
),
),
),
],
),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
initial: () => const SizedBox(),
loading: () => const Center(child: CircularProgressIndicator()),
loaded: (data, a, b, c, tax, d, e, f, g, orderType) => ListTile(
title: const Text('PB1'),
subtitle: Text('tarif pajak ($tax%)'),
contentPadding: EdgeInsets.zero,
textColor: AppColors.primary,
trailing: Checkbox(
value: tax > 0,
onChanged: (value) {
context.read<CheckoutBloc>().add(
CheckoutEvent.addTax(tax > 0 ? 0 : tax),
);
},
),
onTap: () {
context.pop();
},
),
orElse: () => const SizedBox(),
);
},
),
],
),
);
}
}