feat: ticket printer

This commit is contained in:
efrilm 2025-08-07 15:30:05 +07:00
parent ffe076e120
commit 5a260ca3a1
14 changed files with 1245 additions and 58 deletions

View File

@ -1,5 +1,7 @@
import 'dart:developer';
import 'dart:typed_data';
import 'package:barcode_image/barcode_image.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
@ -9,6 +11,8 @@ import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/order_response_model.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:flutter/material.dart';
import 'package:barcode/barcode.dart';
import 'package:image/image.dart' as img;
Future<void> onPrint(
BuildContext context, {
@ -124,3 +128,21 @@ Future<void> onPrintRecipt(
}
}
}
Future<Uint8List> generateBarcodeAsUint8List(String data) async {
// 1. Buat barcode instance (code128, qrCode, dll)
final barcode = Barcode.code128();
// 2. Siapkan canvas image dari package `image`
final image = img.Image(width: 600, height: 200);
// 3. Gambar barcode ke canvas menggunakan barcode_image
drawBarcode(
image,
barcode,
data,
);
// 4. Encode image ke Uint8List PNG
return Uint8List.fromList(img.encodePng(image));
}

View File

@ -1398,4 +1398,39 @@ class PrintDataoutputs {
return bytes;
}
Future<List<int>> printTicket(
int totalPrice, Uint8List imageQris, int paper) async {
List<int> bytes = [];
final profile = await CapabilityProfile.load();
final generator =
Generator(paper == 58 ? PaperSize.mm58 : PaperSize.mm80, profile);
final img.Image? orginalImage = img.decodeImage(imageQris);
bytes += generator.reset();
// final Uint8List bytesData = data.buffer.asUint8List();
// final img.Image? orginalImage = img.decodeImage(bytesData);
// bytes += generator.reset();
bytes += generator.text('Scan Ticket',
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.feed(2);
if (orginalImage != null) {
final img.Image grayscalledImage = img.grayscale(orginalImage);
final img.Image resizedImage =
img.copyResize(grayscalledImage, width: 240);
bytes += generator.imageRaster(resizedImage, align: PosAlign.center);
bytes += generator.feed(1);
}
bytes += generator.text('Price : ${totalPrice.currencyFormatRp}',
styles: const PosStyles(bold: false, align: PosAlign.center));
bytes += generator.feed(4);
bytes += generator.cut();
return bytes;
}
}

View File

@ -18,6 +18,7 @@ import 'package:enaklo_pos/presentation/refund/bloc/refund_bloc.dart';
import 'package:enaklo_pos/presentation/report/blocs/profit_loss/profit_loss_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/order_loader/order_loader_bloc.dart';
import 'package:enaklo_pos/presentation/sales/blocs/payment_form/payment_form_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/upload_file/upload_file_bloc.dart';
import 'package:enaklo_pos/presentation/void/bloc/void_order_bloc.dart';
import 'package:flutter/material.dart';
@ -279,6 +280,9 @@ class _MyAppState extends State<MyApp> {
BlocProvider(
create: (context) => CategoryLoaderBloc(CategoryRemoteDatasource()),
),
BlocProvider(
create: (context) => GetPrinterTicketBloc(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'dart:developer';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:enaklo_pos/presentation/setting/pages/setting_page.dart';
import 'package:enaklo_pos/presentation/table/pages/table_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -15,7 +16,6 @@ import 'package:enaklo_pos/presentation/auth/login_page.dart';
import 'package:enaklo_pos/presentation/report/pages/report_page.dart';
import 'package:enaklo_pos/presentation/setting/bloc/sync_order/sync_order_bloc.dart';
import 'package:enaklo_pos/presentation/setting/pages/printer_configuration_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/settings_page.dart';
import '../../../core/assets/assets.gen.dart';
import '../../auth/bloc/logout/logout_bloc.dart';
@ -61,7 +61,7 @@ class _DashboardPageState extends State<DashboardPage> {
const ReportPage(),
const PrinterConfigurationPage(),
// SalesPage(),
const SettingsPage(),
const SettingPage(),
];
// ignore: unused_local_variable
_connectivitySubscription = Connectivity()

View File

@ -0,0 +1,20 @@
import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/data/models/response/print_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'get_printer_ticket_event.dart';
part 'get_printer_ticket_state.dart';
part 'get_printer_ticket_bloc.freezed.dart';
class GetPrinterTicketBloc
extends Bloc<GetPrinterTicketEvent, GetPrinterTicketState> {
GetPrinterTicketBloc() : super(_Initial()) {
on<_Get>((event, emit) async {
emit(_Loading());
final result =
await ProductLocalDatasource.instance.getPrinterByCode('ticket');
emit(_Success(result));
});
}
}

View File

@ -0,0 +1,609 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'get_printer_ticket_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$GetPrinterTicketEvent {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $GetPrinterTicketEventCopyWith<$Res> {
factory $GetPrinterTicketEventCopyWith(GetPrinterTicketEvent value,
$Res Function(GetPrinterTicketEvent) then) =
_$GetPrinterTicketEventCopyWithImpl<$Res, GetPrinterTicketEvent>;
}
/// @nodoc
class _$GetPrinterTicketEventCopyWithImpl<$Res,
$Val extends GetPrinterTicketEvent>
implements $GetPrinterTicketEventCopyWith<$Res> {
_$GetPrinterTicketEventCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of GetPrinterTicketEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$GetImplCopyWith<$Res> {
factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) =
__$$GetImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$GetImplCopyWithImpl<$Res>
extends _$GetPrinterTicketEventCopyWithImpl<$Res, _$GetImpl>
implements _$$GetImplCopyWith<$Res> {
__$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then)
: super(_value, _then);
/// Create a copy of GetPrinterTicketEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$GetImpl implements _Get {
const _$GetImpl();
@override
String toString() {
return 'GetPrinterTicketEvent.get()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$GetImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() get,
}) {
return get();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? get,
}) {
return get?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? get,
required TResult orElse(),
}) {
if (get != null) {
return get();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Get value) get,
}) {
return get(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Get value)? get,
}) {
return get?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Get value)? get,
required TResult orElse(),
}) {
if (get != null) {
return get(this);
}
return orElse();
}
}
abstract class _Get implements GetPrinterTicketEvent {
const factory _Get() = _$GetImpl;
}
/// @nodoc
mixin _$GetPrinterTicketState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PrintModel? printer) success,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PrintModel? printer)? success,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PrintModel? printer)? success,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $GetPrinterTicketStateCopyWith<$Res> {
factory $GetPrinterTicketStateCopyWith(GetPrinterTicketState value,
$Res Function(GetPrinterTicketState) then) =
_$GetPrinterTicketStateCopyWithImpl<$Res, GetPrinterTicketState>;
}
/// @nodoc
class _$GetPrinterTicketStateCopyWithImpl<$Res,
$Val extends GetPrinterTicketState>
implements $GetPrinterTicketStateCopyWith<$Res> {
_$GetPrinterTicketStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$InitialImplCopyWith<$Res> {
factory _$$InitialImplCopyWith(
_$InitialImpl value, $Res Function(_$InitialImpl) then) =
__$$InitialImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$InitialImplCopyWithImpl<$Res>
extends _$GetPrinterTicketStateCopyWithImpl<$Res, _$InitialImpl>
implements _$$InitialImplCopyWith<$Res> {
__$$InitialImplCopyWithImpl(
_$InitialImpl _value, $Res Function(_$InitialImpl) _then)
: super(_value, _then);
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$InitialImpl implements _Initial {
const _$InitialImpl();
@override
String toString() {
return 'GetPrinterTicketState.initial()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$InitialImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PrintModel? printer) success,
}) {
return initial();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PrintModel? printer)? success,
}) {
return initial?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PrintModel? printer)? success,
required TResult orElse(),
}) {
if (initial != null) {
return initial();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
}) {
return initial(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
}) {
return initial?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
required TResult orElse(),
}) {
if (initial != null) {
return initial(this);
}
return orElse();
}
}
abstract class _Initial implements GetPrinterTicketState {
const factory _Initial() = _$InitialImpl;
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value, $Res Function(_$LoadingImpl) then) =
__$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$GetPrinterTicketStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value, $Res Function(_$LoadingImpl) _then)
: super(_value, _then);
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'GetPrinterTicketState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PrintModel? printer) success,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PrintModel? printer)? success,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PrintModel? printer)? success,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements GetPrinterTicketState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$SuccessImplCopyWith<$Res> {
factory _$$SuccessImplCopyWith(
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
__$$SuccessImplCopyWithImpl<$Res>;
@useResult
$Res call({PrintModel? printer});
}
/// @nodoc
class __$$SuccessImplCopyWithImpl<$Res>
extends _$GetPrinterTicketStateCopyWithImpl<$Res, _$SuccessImpl>
implements _$$SuccessImplCopyWith<$Res> {
__$$SuccessImplCopyWithImpl(
_$SuccessImpl _value, $Res Function(_$SuccessImpl) _then)
: super(_value, _then);
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? printer = freezed,
}) {
return _then(_$SuccessImpl(
freezed == printer
? _value.printer
: printer // ignore: cast_nullable_to_non_nullable
as PrintModel?,
));
}
}
/// @nodoc
class _$SuccessImpl implements _Success {
const _$SuccessImpl(this.printer);
@override
final PrintModel? printer;
@override
String toString() {
return 'GetPrinterTicketState.success(printer: $printer)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SuccessImpl &&
const DeepCollectionEquality().equals(other.printer, printer));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(printer));
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
__$$SuccessImplCopyWithImpl<_$SuccessImpl>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function() loading,
required TResult Function(PrintModel? printer) success,
}) {
return success(printer);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial,
TResult? Function()? loading,
TResult? Function(PrintModel? printer)? success,
}) {
return success?.call(printer);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function()? loading,
TResult Function(PrintModel? printer)? success,
required TResult orElse(),
}) {
if (success != null) {
return success(printer);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Initial value) initial,
required TResult Function(_Loading value) loading,
required TResult Function(_Success value) success,
}) {
return success(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Initial value)? initial,
TResult? Function(_Loading value)? loading,
TResult? Function(_Success value)? success,
}) {
return success?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Initial value)? initial,
TResult Function(_Loading value)? loading,
TResult Function(_Success value)? success,
required TResult orElse(),
}) {
if (success != null) {
return success(this);
}
return orElse();
}
}
abstract class _Success implements GetPrinterTicketState {
const factory _Success(final PrintModel? printer) = _$SuccessImpl;
PrintModel? get printer;
/// Create a copy of GetPrinterTicketState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SuccessImplCopyWith<_$SuccessImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,6 @@
part of 'get_printer_ticket_bloc.dart';
@freezed
class GetPrinterTicketEvent with _$GetPrinterTicketEvent {
const factory GetPrinterTicketEvent.get() = _Get;
}

View File

@ -0,0 +1,8 @@
part of 'get_printer_ticket_bloc.dart';
@freezed
class GetPrinterTicketState with _$GetPrinterTicketState {
const factory GetPrinterTicketState.initial() = _Initial;
const factory GetPrinterTicketState.loading() = _Loading;
const factory GetPrinterTicketState.success(PrintModel? printer) = _Success;
}

View File

@ -0,0 +1,42 @@
import 'package:enaklo_pos/presentation/home/widgets/custom_tab_bar.dart';
import 'package:enaklo_pos/presentation/setting/widgets/bar_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/checker_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/kitchen_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/receipt_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart';
import 'package:enaklo_pos/presentation/setting/widgets/ticket_printer_page.dart';
import 'package:flutter/material.dart';
class SettingPrinterPage extends StatelessWidget {
const SettingPrinterPage({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
SettingsTitle(
'Printer',
subtitle: 'Kelola printer untuk cetak struk',
),
Expanded(
child: CustomTabBarV2(
tabTitles: [
'Receipt Printer',
'Checker Printer',
'Kitchen Printer',
'Bar Printer',
'Tiket Printer',
],
tabViews: [
ReceiptPrinterPage(),
CheckerPrinterPage(),
KitchenPrinterPage(),
BarPrinterPage(),
TicketPrinterPage()
],
),
),
],
);
}
}

View File

@ -0,0 +1,112 @@
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/setting/pages/printer_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/setting_tile.dart';
import 'package:flutter/material.dart';
class SettingPage extends StatefulWidget {
const SettingPage({super.key});
@override
State<SettingPage> createState() => _SettingPageState();
}
class _SettingPageState extends State<SettingPage> {
int currentIndex = 0;
String? role;
void indexValue(int index) {
currentIndex = index;
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: Row(
children: [
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: Material(
color: AppColors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 12.0,
),
width: double.infinity,
height: context.deviceHeight * 0.1,
decoration: const BoxDecoration(
color: AppColors.white,
border: Border(
bottom: BorderSide(
color: AppColors.background,
width: 1.0,
),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Pengaturan',
style: TextStyle(
color: AppColors.black,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
Text(
'Kelola pengaturan aplikasi',
style: TextStyle(
color: AppColors.grey,
fontSize: 14,
),
),
],
),
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
SettingTile(
index: 0,
currentIndex: currentIndex,
title: 'Printer',
subtitle: 'Kelola printer',
icon: Icons.print_outlined,
onTap: () => indexValue(0),
),
],
),
),
),
],
),
),
),
),
Expanded(
flex: 4,
child: Align(
alignment: AlignmentDirectional.topStart,
child: IndexedStack(
index: currentIndex,
children: [
SettingPrinterPage(),
],
),
),
),
],
),
);
}
}

View File

@ -161,62 +161,71 @@ class _ReceiptPrinterPageState extends State<ReceiptPrinterPage> {
SpaceHeight(16),
// button test print
Button.outlined(
onPressed: () async {
if (addressController!.text.isNotEmpty && printNameController!.text.isNotEmpty) {
try {
// Get actual tax and service charge settings
final settingsLocalDatasource = SettingsLocalDatasource();
final taxModel = await settingsLocalDatasource.getTax();
final serviceChargeValue = await settingsLocalDatasource.getServiceCharge();
// Create a test print model
final testPrinter = PrintModel(
code: 'receipt',
name: printNameController!.text,
address: addressController!.text,
paper: paper,
type: selectedPrinter,
);
// Generate test print data
final testPrintData = await PrintDataoutputs.instance.printOrderV3(
[], // Empty product list for test
0,
0,
'Test',
0,
0,
0,
0,
0,
1,
'Test Cashier',
'Test Customer',
int.parse(paper),
taxPercentage: taxModel.value,
serviceChargePercentage: serviceChargeValue,
);
// Print test
await PrinterService().printWithPrinter(
testPrinter,
testPrintData,
context,
);
} catch (e) {
log("Error test printing: $e");
onPressed: () async {
if (addressController!.text.isNotEmpty &&
printNameController!.text.isNotEmpty) {
try {
// Get actual tax and service charge settings
final settingsLocalDatasource =
SettingsLocalDatasource();
final taxModel =
await settingsLocalDatasource.getTax();
final serviceChargeValue =
await settingsLocalDatasource
.getServiceCharge();
// Create a test print model
final testPrinter = PrintModel(
code: 'receipt',
name: printNameController!.text,
address: addressController!.text,
paper: paper,
type: selectedPrinter,
);
// Generate test print data
final testPrintData = await PrintDataoutputs
.instance
.printOrderV3(
[], // Empty product list for test
0,
0,
'Test',
0,
0,
0,
0,
0,
1,
'Test Cashier',
'Test Customer',
int.parse(paper),
taxPercentage: taxModel.value,
serviceChargePercentage: serviceChargeValue,
);
// Print test
await PrinterService().printWithPrinter(
testPrinter,
testPrintData,
context,
);
} catch (e) {
log("Error test printing: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error test printing: $e')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error test printing: $e')),
SnackBar(
content: Text(
'Please fill in printer details first')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Please fill in printer details first')),
);
}
},
label: 'Test Print'
),
},
label: 'Test Print'),
SpaceHeight(8),
// button save
data == null

View File

@ -0,0 +1,310 @@
import 'dart:developer';
import 'package:enaklo_pos/core/function/app_function.dart';
import 'package:enaklo_pos/presentation/setting/bloc/get_printer_ticket/get_printer_ticket_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/components/components.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
import 'package:enaklo_pos/data/models/response/print_model.dart';
import 'package:enaklo_pos/presentation/setting/bloc/create_printer/create_printer_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/update_printer/update_printer_bloc.dart';
import 'package:enaklo_pos/presentation/setting/widgets/dialog_search_printer.dart';
class TicketPrinterPage extends StatefulWidget {
const TicketPrinterPage({super.key});
@override
State<TicketPrinterPage> createState() => _TicketPrinterPageState();
}
class _TicketPrinterPageState extends State<TicketPrinterPage> {
String selectedPrinter = 'Bluetooth';
TextEditingController? addressController;
TextEditingController? printNameController;
String paper = '58';
bool isInitialized = false;
@override
void initState() {
super.initState();
addressController = TextEditingController();
printNameController = TextEditingController();
context.read<GetPrinterTicketBloc>().add(GetPrinterTicketEvent.get());
}
@override
void dispose() {
addressController!.dispose();
printNameController!.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: BlocBuilder<GetPrinterTicketBloc, GetPrinterTicketState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return SizedBox.shrink();
},
loading: () {
return Center(child: CircularProgressIndicator());
},
success: (data) {
if (data != null && !isInitialized) {
addressController!.text = data.address;
printNameController!.text = data.name;
selectedPrinter = data.type;
paper = data.paper;
isInitialized = true;
}
return Container(
// width: 300,
width: context.deviceWidth,
padding: const EdgeInsets.all(16.0),
color: Colors.white,
child: ListView(
children: [
const Text(
'Tiket Printer',
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
SpaceHeight(16),
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Printer Type',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
value: selectedPrinter, // nilai default
items: const [
DropdownMenuItem(
value: 'Bluetooth',
child: Text('Bluetooth'),
),
DropdownMenuItem(
value: 'Network',
child: Text('Network'),
),
],
onChanged: (value) {
selectedPrinter = value ?? 'Bluetooth';
setState(() {});
},
),
SpaceHeight(8),
//button search
selectedPrinter == 'Bluetooth'
? Button.outlined(
onPressed: () {
showDialog(
context: context,
builder: (context) => DialogSearchPrinter(
onSelected: (value) {
addressController!.text = value;
setState(() {});
},
),
);
},
label: 'Search')
: CustomTextField(
controller: addressController!,
label: 'Address',
showLabel: false,
),
SpaceHeight(16),
// Textfield for name
CustomTextField(
controller: printNameController!,
label: 'Print Name',
showLabel: false,
),
SpaceHeight(16),
// Textfield for width paper
DropdownButtonFormField<String>(
decoration: InputDecoration(
labelText: 'Width Paper',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
value: paper, // nilai default
items: const [
DropdownMenuItem(
value: '58',
child: Text('58 mm'),
),
DropdownMenuItem(
value: '80',
child: Text('80 mm'),
),
],
onChanged: (value) {
paper = value ?? '58';
log('Paper : $paper');
},
),
SpaceHeight(16),
// button test print
Button.outlined(
onPressed: () async {
if (addressController!.text.isNotEmpty &&
printNameController!.text.isNotEmpty) {
try {
// Create a test print model
final testPrinter = PrintModel(
code: 'ticket',
name: printNameController!.text,
address: addressController!.text,
paper: paper,
type: selectedPrinter,
);
final barcode =
await generateBarcodeAsUint8List('123');
// Generate test print data
final testPrintData =
await PrintDataoutputs.instance.printTicket(
10000,
barcode,
int.parse(paper),
);
// Print test
await PrinterService().printWithPrinter(
testPrinter,
testPrintData,
context,
);
} catch (e) {
log("Error test printing: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error test printing: $e')),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Please fill in printer details first')),
);
}
},
label: 'Test Print'),
SpaceHeight(8),
// button save
data == null
? BlocConsumer<CreatePrinterBloc, CreatePrinterState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: (message) {
final snackBar = SnackBar(
content: Text(message),
backgroundColor: AppColors.primary,
);
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
context
.read<GetPrinterTicketBloc>()
.add(GetPrinterTicketEvent.get());
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return Button.filled(
onPressed: () {
final printData = PrintModel(
code: 'ticket',
name: printNameController!.text,
address: addressController!.text,
paper: paper,
type: selectedPrinter,
);
context.read<CreatePrinterBloc>().add(
CreatePrinterEvent.createPrinter(
printData),
);
},
label: 'Save',
);
},
loading: () {
return CircularProgressIndicator();
},
);
},
)
: BlocConsumer<UpdatePrinterBloc, UpdatePrinterState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
success: (message) {
final snackBar = SnackBar(
content: Text(message),
backgroundColor: AppColors.primary,
);
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
context
.read<GetPrinterTicketBloc>()
.add(GetPrinterTicketEvent.get());
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return Button.filled(
onPressed: () {
final printData = PrintModel(
id: data.id,
code: 'ticket',
name: printNameController!.text,
address: addressController!.text,
paper: paper,
type: selectedPrinter,
);
log("Print Data : ${printData.toMap()}");
context.read<UpdatePrinterBloc>().add(
UpdatePrinterEvent.updatePrinter(
printData,
),
);
},
label: 'Save',
);
},
loading: () {
return CircularProgressIndicator();
},
);
},
),
SpaceHeight(16),
],
),
);
},
);
},
));
}
}

View File

@ -63,13 +63,21 @@ packages:
source: hosted
version: "1.3.0"
barcode:
dependency: transitive
dependency: "direct main"
description:
name: barcode
sha256: ab180ce22c6555d77d45f0178a523669db67f95856e3378259ef2ffeb43e6003
sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4"
url: "https://pub.dev"
source: hosted
version: "2.2.8"
version: "2.2.9"
barcode_image:
dependency: "direct main"
description:
name: barcode_image
sha256: b93f6ec7fa3d29373cff404ea99fb2afe895d2b4f36704e563f861e7a0818483
url: "https://pub.dev"
source: hosted
version: "2.0.3"
bidi:
dependency: transitive
description:

View File

@ -64,6 +64,8 @@ dependencies:
another_flushbar: ^1.12.30
dropdown_search: ^5.0.6
fl_chart: ^1.0.0
barcode: ^2.2.9
barcode_image: ^2.0.3
# imin_printer: ^0.6.10
dev_dependencies: