diff --git a/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.dart b/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.dart new file mode 100644 index 0000000..3d766cf --- /dev/null +++ b/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.dart @@ -0,0 +1,47 @@ +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; +import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; + +import '../../../../domain/printer/printer.dart'; + +part 'bluetooth_loader_event.dart'; +part 'bluetooth_loader_state.dart'; +part 'bluetooth_loader_bloc.freezed.dart'; + +@injectable +class BluetoothLoaderBloc + extends Bloc { + final IPrinterRepository _printerRepository; + BluetoothLoaderBloc(this._printerRepository) + : super(BluetoothLoaderState.initial()) { + on(_onBluetoothLoaderEvent); + } + + Future _onBluetoothLoaderEvent( + BluetoothLoaderEvent event, + Emitter emit, + ) { + return event.map( + fetched: (e) async { + emit(state.copyWith(isFetching: true, failureOption: none())); + + final result = await _printerRepository.getPairedBluetoothDevices(); + + result.fold( + (failure) => emit( + state.copyWith(failureOption: optionOf(failure), isFetching: false), + ), + (devices) => emit( + state.copyWith( + bluetoothDevices: devices, + failureOption: none(), + isFetching: false, + ), + ), + ); + }, + ); + } +} diff --git a/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.freezed.dart b/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.freezed.dart new file mode 100644 index 0000000..0282786 --- /dev/null +++ b/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.freezed.dart @@ -0,0 +1,379 @@ +// 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 'bluetooth_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$BluetoothLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetched value) fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetched value)? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BluetoothLoaderEventCopyWith<$Res> { + factory $BluetoothLoaderEventCopyWith( + BluetoothLoaderEvent value, + $Res Function(BluetoothLoaderEvent) then, + ) = _$BluetoothLoaderEventCopyWithImpl<$Res, BluetoothLoaderEvent>; +} + +/// @nodoc +class _$BluetoothLoaderEventCopyWithImpl< + $Res, + $Val extends BluetoothLoaderEvent +> + implements $BluetoothLoaderEventCopyWith<$Res> { + _$BluetoothLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of BluetoothLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$FetchedImplCopyWith<$Res> { + factory _$$FetchedImplCopyWith( + _$FetchedImpl value, + $Res Function(_$FetchedImpl) then, + ) = __$$FetchedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$FetchedImplCopyWithImpl<$Res> + extends _$BluetoothLoaderEventCopyWithImpl<$Res, _$FetchedImpl> + implements _$$FetchedImplCopyWith<$Res> { + __$$FetchedImplCopyWithImpl( + _$FetchedImpl _value, + $Res Function(_$FetchedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of BluetoothLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$FetchedImpl implements _Fetched { + const _$FetchedImpl(); + + @override + String toString() { + return 'BluetoothLoaderEvent.fetched()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$FetchedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({required TResult Function() fetched}) { + return fetched(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({TResult? Function()? fetched}) { + return fetched?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Fetched value) fetched, + }) { + return fetched(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Fetched value)? fetched, + }) { + return fetched?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(this); + } + return orElse(); + } +} + +abstract class _Fetched implements BluetoothLoaderEvent { + const factory _Fetched() = _$FetchedImpl; +} + +/// @nodoc +mixin _$BluetoothLoaderState { + List get bluetoothDevices => + throw _privateConstructorUsedError; + Option get failureOption => + throw _privateConstructorUsedError; + bool get isFetching => throw _privateConstructorUsedError; + + /// Create a copy of BluetoothLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $BluetoothLoaderStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $BluetoothLoaderStateCopyWith<$Res> { + factory $BluetoothLoaderStateCopyWith( + BluetoothLoaderState value, + $Res Function(BluetoothLoaderState) then, + ) = _$BluetoothLoaderStateCopyWithImpl<$Res, BluetoothLoaderState>; + @useResult + $Res call({ + List bluetoothDevices, + Option failureOption, + bool isFetching, + }); +} + +/// @nodoc +class _$BluetoothLoaderStateCopyWithImpl< + $Res, + $Val extends BluetoothLoaderState +> + implements $BluetoothLoaderStateCopyWith<$Res> { + _$BluetoothLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of BluetoothLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? bluetoothDevices = null, + Object? failureOption = null, + Object? isFetching = null, + }) { + return _then( + _value.copyWith( + bluetoothDevices: null == bluetoothDevices + ? _value.bluetoothDevices + : bluetoothDevices // ignore: cast_nullable_to_non_nullable + as List, + failureOption: null == failureOption + ? _value.failureOption + : failureOption // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$BluetoothLoaderStateImplCopyWith<$Res> + implements $BluetoothLoaderStateCopyWith<$Res> { + factory _$$BluetoothLoaderStateImplCopyWith( + _$BluetoothLoaderStateImpl value, + $Res Function(_$BluetoothLoaderStateImpl) then, + ) = __$$BluetoothLoaderStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List bluetoothDevices, + Option failureOption, + bool isFetching, + }); +} + +/// @nodoc +class __$$BluetoothLoaderStateImplCopyWithImpl<$Res> + extends _$BluetoothLoaderStateCopyWithImpl<$Res, _$BluetoothLoaderStateImpl> + implements _$$BluetoothLoaderStateImplCopyWith<$Res> { + __$$BluetoothLoaderStateImplCopyWithImpl( + _$BluetoothLoaderStateImpl _value, + $Res Function(_$BluetoothLoaderStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of BluetoothLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? bluetoothDevices = null, + Object? failureOption = null, + Object? isFetching = null, + }) { + return _then( + _$BluetoothLoaderStateImpl( + bluetoothDevices: null == bluetoothDevices + ? _value._bluetoothDevices + : bluetoothDevices // ignore: cast_nullable_to_non_nullable + as List, + failureOption: null == failureOption + ? _value.failureOption + : failureOption // ignore: cast_nullable_to_non_nullable + as Option, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$BluetoothLoaderStateImpl implements _BluetoothLoaderState { + _$BluetoothLoaderStateImpl({ + required final List bluetoothDevices, + required this.failureOption, + this.isFetching = false, + }) : _bluetoothDevices = bluetoothDevices; + + final List _bluetoothDevices; + @override + List get bluetoothDevices { + if (_bluetoothDevices is EqualUnmodifiableListView) + return _bluetoothDevices; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_bluetoothDevices); + } + + @override + final Option failureOption; + @override + @JsonKey() + final bool isFetching; + + @override + String toString() { + return 'BluetoothLoaderState(bluetoothDevices: $bluetoothDevices, failureOption: $failureOption, isFetching: $isFetching)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$BluetoothLoaderStateImpl && + const DeepCollectionEquality().equals( + other._bluetoothDevices, + _bluetoothDevices, + ) && + (identical(other.failureOption, failureOption) || + other.failureOption == failureOption) && + (identical(other.isFetching, isFetching) || + other.isFetching == isFetching)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_bluetoothDevices), + failureOption, + isFetching, + ); + + /// Create a copy of BluetoothLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$BluetoothLoaderStateImplCopyWith<_$BluetoothLoaderStateImpl> + get copyWith => + __$$BluetoothLoaderStateImplCopyWithImpl<_$BluetoothLoaderStateImpl>( + this, + _$identity, + ); +} + +abstract class _BluetoothLoaderState implements BluetoothLoaderState { + factory _BluetoothLoaderState({ + required final List bluetoothDevices, + required final Option failureOption, + final bool isFetching, + }) = _$BluetoothLoaderStateImpl; + + @override + List get bluetoothDevices; + @override + Option get failureOption; + @override + bool get isFetching; + + /// Create a copy of BluetoothLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$BluetoothLoaderStateImplCopyWith<_$BluetoothLoaderStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_event.dart b/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_event.dart new file mode 100644 index 0000000..9c3db9d --- /dev/null +++ b/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_event.dart @@ -0,0 +1,6 @@ +part of 'bluetooth_loader_bloc.dart'; + +@freezed +class BluetoothLoaderEvent with _$BluetoothLoaderEvent { + const factory BluetoothLoaderEvent.fetched() = _Fetched; +} diff --git a/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_state.dart b/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_state.dart new file mode 100644 index 0000000..015e7af --- /dev/null +++ b/lib/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_state.dart @@ -0,0 +1,13 @@ +part of 'bluetooth_loader_bloc.dart'; + +@freezed +class BluetoothLoaderState with _$BluetoothLoaderState { + factory BluetoothLoaderState({ + required List bluetoothDevices, + required Option failureOption, + @Default(false) bool isFetching, + }) = _BluetoothLoaderState; + + factory BluetoothLoaderState.initial() => + BluetoothLoaderState(bluetoothDevices: [], failureOption: none()); +} diff --git a/lib/application/printer/printer_bloc.dart b/lib/application/printer/printer_bloc.dart new file mode 100644 index 0000000..61c34b7 --- /dev/null +++ b/lib/application/printer/printer_bloc.dart @@ -0,0 +1,22 @@ +import 'package:bloc/bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +part 'printer_event.dart'; +part 'printer_state.dart'; +part 'printer_bloc.freezed.dart'; + +@injectable +class PrinterBloc extends Bloc { + PrinterBloc() : super(PrinterState.initial()) { + on(_onPrinterEvent); + } + + Future _onPrinterEvent(PrinterEvent event, Emitter emit) { + return event.map( + indexChanged: (e) async { + emit(state.copyWith(index: e.index)); + }, + ); + } +} diff --git a/lib/application/printer/printer_bloc.freezed.dart b/lib/application/printer/printer_bloc.freezed.dart new file mode 100644 index 0000000..57ff32b --- /dev/null +++ b/lib/application/printer/printer_bloc.freezed.dart @@ -0,0 +1,363 @@ +// 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 'printer_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$PrinterEvent { + int get index => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(int index) indexChanged, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int index)? indexChanged, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int index)? indexChanged, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_IndexChanged value) indexChanged, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_IndexChanged value)? indexChanged, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_IndexChanged value)? indexChanged, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + + /// Create a copy of PrinterEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PrinterEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PrinterEventCopyWith<$Res> { + factory $PrinterEventCopyWith( + PrinterEvent value, + $Res Function(PrinterEvent) then, + ) = _$PrinterEventCopyWithImpl<$Res, PrinterEvent>; + @useResult + $Res call({int index}); +} + +/// @nodoc +class _$PrinterEventCopyWithImpl<$Res, $Val extends PrinterEvent> + implements $PrinterEventCopyWith<$Res> { + _$PrinterEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PrinterEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? index = null}) { + return _then( + _value.copyWith( + index: null == index + ? _value.index + : index // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$IndexChangedImplCopyWith<$Res> + implements $PrinterEventCopyWith<$Res> { + factory _$$IndexChangedImplCopyWith( + _$IndexChangedImpl value, + $Res Function(_$IndexChangedImpl) then, + ) = __$$IndexChangedImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int index}); +} + +/// @nodoc +class __$$IndexChangedImplCopyWithImpl<$Res> + extends _$PrinterEventCopyWithImpl<$Res, _$IndexChangedImpl> + implements _$$IndexChangedImplCopyWith<$Res> { + __$$IndexChangedImplCopyWithImpl( + _$IndexChangedImpl _value, + $Res Function(_$IndexChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PrinterEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? index = null}) { + return _then( + _$IndexChangedImpl( + null == index + ? _value.index + : index // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$IndexChangedImpl implements _IndexChanged { + const _$IndexChangedImpl(this.index); + + @override + final int index; + + @override + String toString() { + return 'PrinterEvent.indexChanged(index: $index)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IndexChangedImpl && + (identical(other.index, index) || other.index == index)); + } + + @override + int get hashCode => Object.hash(runtimeType, index); + + /// Create a copy of PrinterEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$IndexChangedImplCopyWith<_$IndexChangedImpl> get copyWith => + __$$IndexChangedImplCopyWithImpl<_$IndexChangedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(int index) indexChanged, + }) { + return indexChanged(index); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(int index)? indexChanged, + }) { + return indexChanged?.call(index); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(int index)? indexChanged, + required TResult orElse(), + }) { + if (indexChanged != null) { + return indexChanged(index); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_IndexChanged value) indexChanged, + }) { + return indexChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_IndexChanged value)? indexChanged, + }) { + return indexChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_IndexChanged value)? indexChanged, + required TResult orElse(), + }) { + if (indexChanged != null) { + return indexChanged(this); + } + return orElse(); + } +} + +abstract class _IndexChanged implements PrinterEvent { + const factory _IndexChanged(final int index) = _$IndexChangedImpl; + + @override + int get index; + + /// Create a copy of PrinterEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$IndexChangedImplCopyWith<_$IndexChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$PrinterState { + int get index => throw _privateConstructorUsedError; + + /// Create a copy of PrinterState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PrinterStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PrinterStateCopyWith<$Res> { + factory $PrinterStateCopyWith( + PrinterState value, + $Res Function(PrinterState) then, + ) = _$PrinterStateCopyWithImpl<$Res, PrinterState>; + @useResult + $Res call({int index}); +} + +/// @nodoc +class _$PrinterStateCopyWithImpl<$Res, $Val extends PrinterState> + implements $PrinterStateCopyWith<$Res> { + _$PrinterStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PrinterState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? index = null}) { + return _then( + _value.copyWith( + index: null == index + ? _value.index + : index // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$PrinterStateImplCopyWith<$Res> + implements $PrinterStateCopyWith<$Res> { + factory _$$PrinterStateImplCopyWith( + _$PrinterStateImpl value, + $Res Function(_$PrinterStateImpl) then, + ) = __$$PrinterStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({int index}); +} + +/// @nodoc +class __$$PrinterStateImplCopyWithImpl<$Res> + extends _$PrinterStateCopyWithImpl<$Res, _$PrinterStateImpl> + implements _$$PrinterStateImplCopyWith<$Res> { + __$$PrinterStateImplCopyWithImpl( + _$PrinterStateImpl _value, + $Res Function(_$PrinterStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PrinterState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? index = null}) { + return _then( + _$PrinterStateImpl( + index: null == index + ? _value.index + : index // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$PrinterStateImpl implements _PrinterState { + _$PrinterStateImpl({this.index = 0}); + + @override + @JsonKey() + final int index; + + @override + String toString() { + return 'PrinterState(index: $index)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PrinterStateImpl && + (identical(other.index, index) || other.index == index)); + } + + @override + int get hashCode => Object.hash(runtimeType, index); + + /// Create a copy of PrinterState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PrinterStateImplCopyWith<_$PrinterStateImpl> get copyWith => + __$$PrinterStateImplCopyWithImpl<_$PrinterStateImpl>(this, _$identity); +} + +abstract class _PrinterState implements PrinterState { + factory _PrinterState({final int index}) = _$PrinterStateImpl; + + @override + int get index; + + /// Create a copy of PrinterState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PrinterStateImplCopyWith<_$PrinterStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/printer/printer_event.dart b/lib/application/printer/printer_event.dart new file mode 100644 index 0000000..0979688 --- /dev/null +++ b/lib/application/printer/printer_event.dart @@ -0,0 +1,6 @@ +part of 'printer_bloc.dart'; + +@freezed +class PrinterEvent with _$PrinterEvent { + const factory PrinterEvent.indexChanged(int index) = _IndexChanged; +} diff --git a/lib/application/printer/printer_state.dart b/lib/application/printer/printer_state.dart new file mode 100644 index 0000000..add2c73 --- /dev/null +++ b/lib/application/printer/printer_state.dart @@ -0,0 +1,7 @@ +part of 'printer_bloc.dart'; + +@freezed +class PrinterState with _$PrinterState { + factory PrinterState({@Default(0) int index}) = _PrinterState; + factory PrinterState.initial() => PrinterState(); +} diff --git a/lib/common/data/printer_data.dart b/lib/common/data/printer_data.dart new file mode 100644 index 0000000..59e828f --- /dev/null +++ b/lib/common/data/printer_data.dart @@ -0,0 +1,2 @@ +List printerTypes = ['Bluetooth', 'Network']; +List paperTypes = ['58', '80']; diff --git a/lib/common/function/app_function.dart b/lib/common/function/app_function.dart index 35862e6..0b4cc7a 100644 --- a/lib/common/function/app_function.dart +++ b/lib/common/function/app_function.dart @@ -1,4 +1,7 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../injection.dart'; @@ -54,3 +57,15 @@ int safeRound(double value) { if (value.isNaN || value.isInfinite) return 0; return value.round(); } + +Future loadPermissionBluetooth() async { + var status2 = await Permission.bluetoothScan.status; + log("Permission: $status2"); + if (status2.isDenied) { + await Permission.bluetoothScan.request(); + } + var status = await Permission.bluetoothConnect.status; + if (status.isDenied) { + await Permission.bluetoothConnect.request(); + } +} diff --git a/lib/domain/printer/entities/print_entity.dart b/lib/domain/printer/entities/print_entity.dart new file mode 100644 index 0000000..23b45a2 --- /dev/null +++ b/lib/domain/printer/entities/print_entity.dart @@ -0,0 +1 @@ +part of '../printer.dart'; diff --git a/lib/domain/printer/failures/printer_failure.dart b/lib/domain/printer/failures/printer_failure.dart new file mode 100644 index 0000000..540c7f5 --- /dev/null +++ b/lib/domain/printer/failures/printer_failure.dart @@ -0,0 +1,12 @@ +part of '../printer.dart'; + +@freezed +sealed class PrinterFailure with _$PrinterFailure { + const factory PrinterFailure.serverError(ApiFailure failure) = _ServerError; + const factory PrinterFailure.unexpectedError() = _UnexpectedError; + const factory PrinterFailure.empty() = _Empty; + const factory PrinterFailure.localStorageError(String erroMessage) = + _LocalStorageError; + const factory PrinterFailure.dynamicErrorMessage(String erroMessage) = + _DynamicErrorMessage; +} diff --git a/lib/domain/printer/printer.dart b/lib/domain/printer/printer.dart new file mode 100644 index 0000000..2c060e2 --- /dev/null +++ b/lib/domain/printer/printer.dart @@ -0,0 +1,11 @@ +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; + +import '../../common/api/api_failure.dart'; + +part 'printer.freezed.dart'; + +part 'entities/print_entity.dart'; +part 'failures/printer_failure.dart'; +part 'repositories/i_printer_repository.dart'; diff --git a/lib/domain/printer/printer.freezed.dart b/lib/domain/printer/printer.freezed.dart new file mode 100644 index 0000000..8ba8470 --- /dev/null +++ b/lib/domain/printer/printer.freezed.dart @@ -0,0 +1,844 @@ +// 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 'printer.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$PrinterFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PrinterFailureCopyWith<$Res> { + factory $PrinterFailureCopyWith( + PrinterFailure value, + $Res Function(PrinterFailure) then, + ) = _$PrinterFailureCopyWithImpl<$Res, PrinterFailure>; +} + +/// @nodoc +class _$PrinterFailureCopyWithImpl<$Res, $Val extends PrinterFailure> + implements $PrinterFailureCopyWith<$Res> { + _$PrinterFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({ApiFailure failure}); + + $ApiFailureCopyWith<$Res> get failure; +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$PrinterFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failure = null}) { + return _then( + _$ServerErrorImpl( + null == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as ApiFailure, + ), + ); + } + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ApiFailureCopyWith<$Res> get failure { + return $ApiFailureCopyWith<$Res>(_value.failure, (value) { + return _then(_value.copyWith(failure: value)); + }); + } +} + +/// @nodoc + +class _$ServerErrorImpl implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString() { + return 'PrinterFailure.serverError(failure: $failure)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ServerErrorImpl && + (identical(other.failure, failure) || other.failure == failure)); + } + + @override + int get hashCode => Object.hash(runtimeType, failure); + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return serverError(failure); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return serverError?.call(failure); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(failure); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return serverError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return serverError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError implements PrinterFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$PrinterFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString() { + return 'PrinterFailure.unexpectedError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnexpectedErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return unexpectedError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return unexpectedError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return unexpectedError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return unexpectedError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError implements PrinterFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$EmptyImplCopyWith<$Res> { + factory _$$EmptyImplCopyWith( + _$EmptyImpl value, + $Res Function(_$EmptyImpl) then, + ) = __$$EmptyImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$EmptyImplCopyWithImpl<$Res> + extends _$PrinterFailureCopyWithImpl<$Res, _$EmptyImpl> + implements _$$EmptyImplCopyWith<$Res> { + __$$EmptyImplCopyWithImpl( + _$EmptyImpl _value, + $Res Function(_$EmptyImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$EmptyImpl implements _Empty { + const _$EmptyImpl(); + + @override + String toString() { + return 'PrinterFailure.empty()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$EmptyImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return empty(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return empty?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (empty != null) { + return empty(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return empty(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return empty?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (empty != null) { + return empty(this); + } + return orElse(); + } +} + +abstract class _Empty implements PrinterFailure { + const factory _Empty() = _$EmptyImpl; +} + +/// @nodoc +abstract class _$$LocalStorageErrorImplCopyWith<$Res> { + factory _$$LocalStorageErrorImplCopyWith( + _$LocalStorageErrorImpl value, + $Res Function(_$LocalStorageErrorImpl) then, + ) = __$$LocalStorageErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$LocalStorageErrorImplCopyWithImpl<$Res> + extends _$PrinterFailureCopyWithImpl<$Res, _$LocalStorageErrorImpl> + implements _$$LocalStorageErrorImplCopyWith<$Res> { + __$$LocalStorageErrorImplCopyWithImpl( + _$LocalStorageErrorImpl _value, + $Res Function(_$LocalStorageErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$LocalStorageErrorImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$LocalStorageErrorImpl implements _LocalStorageError { + const _$LocalStorageErrorImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'PrinterFailure.localStorageError(erroMessage: $erroMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LocalStorageErrorImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LocalStorageErrorImplCopyWith<_$LocalStorageErrorImpl> get copyWith => + __$$LocalStorageErrorImplCopyWithImpl<_$LocalStorageErrorImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return localStorageError(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return localStorageError?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (localStorageError != null) { + return localStorageError(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return localStorageError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return localStorageError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (localStorageError != null) { + return localStorageError(this); + } + return orElse(); + } +} + +abstract class _LocalStorageError implements PrinterFailure { + const factory _LocalStorageError(final String erroMessage) = + _$LocalStorageErrorImpl; + + String get erroMessage; + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LocalStorageErrorImplCopyWith<_$LocalStorageErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$DynamicErrorMessageImplCopyWith<$Res> { + factory _$$DynamicErrorMessageImplCopyWith( + _$DynamicErrorMessageImpl value, + $Res Function(_$DynamicErrorMessageImpl) then, + ) = __$$DynamicErrorMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$DynamicErrorMessageImplCopyWithImpl<$Res> + extends _$PrinterFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$DynamicErrorMessageImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$DynamicErrorMessageImpl implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'PrinterFailure.dynamicErrorMessage(erroMessage: $erroMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DynamicErrorMessageImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + __$$DynamicErrorMessageImplCopyWithImpl<_$DynamicErrorMessageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return dynamicErrorMessage(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return dynamicErrorMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(this); + } + return orElse(); + } +} + +abstract class _DynamicErrorMessage implements PrinterFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of PrinterFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/printer/repositories/i_printer_repository.dart b/lib/domain/printer/repositories/i_printer_repository.dart new file mode 100644 index 0000000..b291962 --- /dev/null +++ b/lib/domain/printer/repositories/i_printer_repository.dart @@ -0,0 +1,9 @@ +part of '../printer.dart'; + +abstract class IPrinterRepository { + Future> connectBluetooth(String macAddress); + Future> disconectBluetooth(); + Future> isBluetoothEnabled(); + Future>> + getPairedBluetoothDevices(); +} diff --git a/lib/infrastructure/printer/printer_dtos.dart b/lib/infrastructure/printer/printer_dtos.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/infrastructure/printer/repositories/printer_repository.dart b/lib/infrastructure/printer/repositories/printer_repository.dart new file mode 100644 index 0000000..eba5de4 --- /dev/null +++ b/lib/infrastructure/printer/repositories/printer_repository.dart @@ -0,0 +1,120 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:injectable/injectable.dart'; +import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart'; + +import '../../../domain/printer/printer.dart'; + +@Injectable(as: IPrinterRepository) +class PrinterRepository implements IPrinterRepository { + final _logName = 'PrinterRepository'; + PrinterRepository(); + + @override + Future> connectBluetooth( + String macAddress, + ) async { + try { + bool isConnected = await PrintBluetoothThermal.connectionStatus; + if (isConnected) { + log("Already connected to Bluetooth printer", name: _logName); + return right(true); + } + + bool connected = await PrintBluetoothThermal.connect( + macPrinterAddress: macAddress, + ); + + if (connected) { + log( + "Successfully connected to Bluetooth printer: $macAddress", + name: _logName, + ); + } else { + FirebaseCrashlytics.instance.recordError( + 'Failed to connect to Bluetooth printer', + null, + reason: 'Failed to connect to Bluetooth printe', + information: [ + 'function: connectBluetoothPrinter(String macAddress)', + 'macAddress: $macAddress', + ], + ); + log( + "Failed to connect to Bluetooth printer: $macAddress", + name: _logName, + ); + } + + return right(connected); + } catch (e, stackTrace) { + FirebaseCrashlytics.instance.recordError( + e, + stackTrace, + reason: 'Error connecting to Bluetooth printer', + information: [ + 'function: connectBluetoothPrinter(String macAddress)', + 'Printer: Bluetooth printer', + 'macAddress: $macAddress', + ], + ); + log("Error connecting to Bluetooth printer", name: _logName, error: e); + return left( + PrinterFailure.dynamicErrorMessage( + 'Error connecting to Bluetooth printer', + ), + ); + } + } + + @override + Future> disconectBluetooth() async { + try { + bool result = await PrintBluetoothThermal.disconnect; + log("Bluetooth printer disconnected: $result", name: _logName); + return right(result); + } catch (e) { + log("Error disconnecting Bluetooth printer", error: e, name: _logName); + return left( + PrinterFailure.dynamicErrorMessage( + 'Error disconnecting Bluetooth printer', + ), + ); + } + } + + @override + Future>> + getPairedBluetoothDevices() async { + try { + final result = await PrintBluetoothThermal.pairedBluetooths; + + log("Paired Bluetooth devices: $result", name: _logName); + + return right(result); + } catch (e) { + log("Error getting paired Bluetooth devices", name: _logName, error: e); + return left( + PrinterFailure.dynamicErrorMessage( + 'Error getting paired Bluetooth devices', + ), + ); + } + } + + @override + Future> isBluetoothEnabled() async { + try { + final result = await PrintBluetoothThermal.bluetoothEnabled; + + return right(result); + } catch (e) { + log("Error checking Bluetooth status", name: _logName, error: e); + return left( + PrinterFailure.dynamicErrorMessage('Error checking Bluetooth status'), + ); + } + } +} diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 25ae30a..3a459e7 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -42,6 +42,10 @@ import 'package:apskel_pos_flutter_v2/application/payment/payment_form/payment_f as _i194; import 'package:apskel_pos_flutter_v2/application/payment_method/payment_method_loader/payment_method_loader_bloc.dart' as _i952; +import 'package:apskel_pos_flutter_v2/application/printer/bluetooth/bluetooth_loader/bluetooth_loader_bloc.dart' + as _i903; +import 'package:apskel_pos_flutter_v2/application/printer/printer_bloc.dart' + as _i96; import 'package:apskel_pos_flutter_v2/application/product/product_loader/product_loader_bloc.dart' as _i13; import 'package:apskel_pos_flutter_v2/application/refund/refund_form/refund_form_bloc.dart' @@ -76,6 +80,7 @@ import 'package:apskel_pos_flutter_v2/domain/order/order.dart' as _i299; import 'package:apskel_pos_flutter_v2/domain/outlet/outlet.dart' as _i552; import 'package:apskel_pos_flutter_v2/domain/payment_method/payment_method.dart' as _i297; +import 'package:apskel_pos_flutter_v2/domain/printer/printer.dart' as _i104; import 'package:apskel_pos_flutter_v2/domain/product/product.dart' as _i44; import 'package:apskel_pos_flutter_v2/domain/table/table.dart' as _i983; import 'package:apskel_pos_flutter_v2/env.dart' as _i923; @@ -113,6 +118,8 @@ import 'package:apskel_pos_flutter_v2/infrastructure/payment_method/datasources/ as _i833; import 'package:apskel_pos_flutter_v2/infrastructure/payment_method/repositories/payment_method_repository.dart' as _i167; +import 'package:apskel_pos_flutter_v2/infrastructure/printer/repositories/printer_repository.dart' + as _i881; import 'package:apskel_pos_flutter_v2/infrastructure/product/datasources/local_data_provider.dart' as _i464; import 'package:apskel_pos_flutter_v2/infrastructure/product/datasources/remote_data_provider.dart' @@ -151,6 +158,7 @@ extension GetItInjectableX on _i174.GetIt { preResolve: true, ); gh.factory<_i334.SplitBillFormBloc>(() => _i334.SplitBillFormBloc()); + gh.factory<_i96.PrinterBloc>(() => _i96.PrinterBloc()); gh.factory<_i13.CheckoutFormBloc>(() => _i13.CheckoutFormBloc()); gh.factory<_i257.ReportBloc>(() => _i257.ReportBloc()); gh.singleton<_i487.DatabaseHelper>(() => databaseDi.databaseHelper); @@ -161,6 +169,7 @@ extension GetItInjectableX on _i174.GetIt { () => _i171.NetworkClient(gh<_i895.Connectivity>()), ); gh.factory<_i923.Env>(() => _i923.DevEnv(), registerFor: {_dev}); + gh.factory<_i104.IPrinterRepository>(() => _i881.PrinterRepository()); gh.factory<_i708.CategoryLocalDataProvider>( () => _i708.CategoryLocalDataProvider(gh<_i487.DatabaseHelper>()), ); @@ -173,6 +182,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i693.OutletLocalDatasource>( () => _i693.OutletLocalDatasource(gh<_i460.SharedPreferences>()), ); + gh.factory<_i903.BluetoothLoaderBloc>( + () => _i903.BluetoothLoaderBloc(gh<_i104.IPrinterRepository>()), + ); gh.lazySingleton<_i457.ApiClient>( () => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()), ); @@ -302,28 +314,28 @@ extension GetItInjectableX on _i174.GetIt { gh<_i502.ICategoryRepository>(), ), ); - gh.factory<_i80.DashboardAnalyticLoaderBloc>( - () => _i80.DashboardAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), + gh.factory<_i268.ProductAnalyticLoaderBloc>( + () => _i268.ProductAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), + ); + gh.factory<_i651.InventoryAnalyticLoaderBloc>( + () => _i651.InventoryAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), + ); + gh.factory<_i741.ProfitLossAnalyticLoaderBloc>( + () => _i741.ProfitLossAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), ); gh.factory<_i413.SalesAnalyticLoaderBloc>( () => _i413.SalesAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), ); - gh.factory<_i268.ProductAnalyticLoaderBloc>( - () => _i268.ProductAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), - ); gh.factory<_i733.PaymentMethodAnalyticLoaderBloc>( () => _i733.PaymentMethodAnalyticLoaderBloc( gh<_i346.IAnalyticRepository>(), ), ); - gh.factory<_i741.ProfitLossAnalyticLoaderBloc>( - () => _i741.ProfitLossAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), - ); gh.factory<_i911.CategoryAnalyticLoaderBloc>( () => _i911.CategoryAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), ); - gh.factory<_i651.InventoryAnalyticLoaderBloc>( - () => _i651.InventoryAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), + gh.factory<_i80.DashboardAnalyticLoaderBloc>( + () => _i80.DashboardAnalyticLoaderBloc(gh<_i346.IAnalyticRepository>()), ); return this; } diff --git a/lib/presentation/app_widget.dart b/lib/presentation/app_widget.dart index b0c71bf..597e55f 100644 --- a/lib/presentation/app_widget.dart +++ b/lib/presentation/app_widget.dart @@ -9,6 +9,7 @@ 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_loader/bluetooth_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'; @@ -45,6 +46,7 @@ class _AppWidgetState extends State { BlocProvider(create: (context) => getIt()), BlocProvider(create: (context) => getIt()), BlocProvider(create: (context) => getIt()), + BlocProvider(create: (context) => getIt()), ], child: MaterialApp.router( debugShowCheckedModeBanner: false, diff --git a/lib/presentation/components/dialog/dialog.dart b/lib/presentation/components/dialog/dialog.dart index 3721da9..a2cf5f7 100644 --- a/lib/presentation/components/dialog/dialog.dart +++ b/lib/presentation/components/dialog/dialog.dart @@ -4,6 +4,7 @@ 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_loader/bluetooth_loader_bloc.dart'; import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; import '../../../common/types/order_type.dart'; @@ -19,3 +20,4 @@ part 'outlet_dialog.dart'; part 'variant_dialog.dart'; part 'delivery_dialog.dart'; part 'order_type_dialog.dart'; +part 'printer_bluetooth_dialog.dart'; diff --git a/lib/presentation/components/dialog/printer_bluetooth_dialog.dart b/lib/presentation/components/dialog/printer_bluetooth_dialog.dart new file mode 100644 index 0000000..bec413c --- /dev/null +++ b/lib/presentation/components/dialog/printer_bluetooth_dialog.dart @@ -0,0 +1,77 @@ +part of 'dialog.dart'; + +class PrinterBluetoothDialog extends StatefulWidget { + final Function(String) onSelected; + + const PrinterBluetoothDialog({super.key, required this.onSelected}); + + @override + State createState() => _PrinterBluetoothDialogState(); +} + +class _PrinterBluetoothDialogState extends State { + @override + void initState() { + context.read().add(BluetoothLoaderEvent.fetched()); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return CustomModalDialog( + title: 'Bluetooth', + contentPadding: EdgeInsets.all(16), + minHeight: context.deviceHeight * 0.6, + minWidth: context.deviceWidth * 0.4, + child: BlocBuilder( + builder: (context, state) { + if (state.isFetching) { + return Center(child: LoaderWithText()); + } + + if (state.bluetoothDevices.isEmpty) { + return const Center(child: Text('No bluetooth printer found')); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + 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), + ), + ), + 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, + ), + ), + ], + ), + ), + ) + .toList(), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/components/field/dropdown_search.dart b/lib/presentation/components/field/dropdown_search.dart new file mode 100644 index 0000000..5191800 --- /dev/null +++ b/lib/presentation/components/field/dropdown_search.dart @@ -0,0 +1,217 @@ +part of 'field.dart'; + +class AppDropdownSearch extends StatelessWidget { + final String label; + final String hintText; + final String searchHint; + final IconData prefixIcon; + final List items; + final T? selectedItem; + final String Function(T) itemAsString; + final bool Function(T?, T?)? compareFn; + final void Function(T?)? onChanged; + final String? Function(T?)? validator; + final String emptyMessage; + final String Function(String)? emptySearchMessage; + final Widget Function(BuildContext, T, bool)? itemBuilder; + final bool showSearchBox; + final bool isRequired; + + const AppDropdownSearch({ + Key? key, + required this.label, + required this.hintText, + required this.items, + required this.itemAsString, + this.searchHint = "Cari...", + this.prefixIcon = Icons.category_outlined, + this.selectedItem, + this.compareFn, + this.onChanged, + this.validator, + this.emptyMessage = "Tidak ada data tersedia", + this.emptySearchMessage, + this.itemBuilder, + this.showSearchBox = true, + this.isRequired = false, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Label + Row( + children: [ + Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.black, + fontWeight: FontWeight.w600, + ), + ), + if (isRequired) + Text( + ' *', + style: TextStyle( + fontSize: 14, + color: Colors.red, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + const SizedBox(height: 8), + + // Dropdown + DropdownSearch( + items: items, + selectedItem: selectedItem, + + // Dropdown properties + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + hintText: hintText, + hintStyle: TextStyle(color: AppColor.textSecondary, fontSize: 14), + prefixIcon: Icon( + prefixIcon, + color: AppColor.textSecondary, + size: 20, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColor.border, width: 1.5), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColor.border, width: 1.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColor.primary, width: 2), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColor.error, width: 1.5), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: AppColor.error, width: 2), + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + ), + ), + + // Popup properties + popupProps: PopupProps.menu( + showSearchBox: showSearchBox, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: searchHint, + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + menuProps: MenuProps( + backgroundColor: Colors.white, + elevation: 8, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + itemBuilder: itemBuilder ?? _defaultItemBuilder, + emptyBuilder: (context, searchEntry) { + return Container( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.search_off, + color: Colors.grey.shade400, + size: 48, + ), + const SizedBox(height: 12), + Text( + searchEntry.isEmpty + ? emptyMessage + : emptySearchMessage?.call(searchEntry) ?? + "Tidak ditemukan data dengan '$searchEntry'", + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + }, + ), + + // Item as string (for search functionality) + itemAsString: itemAsString, + + // Comparison function + compareFn: compareFn, + + // On changed callback + onChanged: onChanged, + + // Validator + validator: validator, + ), + ], + ); + } + + Widget _defaultItemBuilder(BuildContext context, T item, bool isSelected) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: isSelected ? Colors.blue.shade50 : Colors.transparent, + border: Border( + bottom: BorderSide(color: Colors.grey.shade100, width: 0.5), + ), + ), + child: Row( + children: [ + Container( + width: 8, + height: 8, + decoration: BoxDecoration( + color: isSelected ? Colors.blue.shade600 : Colors.grey.shade400, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + itemAsString(item), + style: TextStyle( + fontSize: 14, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, + color: isSelected ? Colors.blue.shade700 : Colors.black87, + ), + ), + ), + if (isSelected) + Icon(Icons.check, color: Colors.blue.shade600, size: 18), + ], + ), + ); + } +} diff --git a/lib/presentation/components/field/field.dart b/lib/presentation/components/field/field.dart index 1dca7ac..f43d05e 100644 --- a/lib/presentation/components/field/field.dart +++ b/lib/presentation/components/field/field.dart @@ -1,3 +1,4 @@ +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -11,3 +12,4 @@ part 'password_text_field.dart'; part 'text_field.dart'; part 'search_text_field.dart'; part 'customer_autocomplete.dart'; +part 'dropdown_search.dart'; diff --git a/lib/presentation/components/tab/custom_tabbar.dart b/lib/presentation/components/tab/custom_tabbar.dart new file mode 100644 index 0000000..68089d9 --- /dev/null +++ b/lib/presentation/components/tab/custom_tabbar.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; + +class CustomTabBar extends StatelessWidget { + final List tabTitles; + final List tabViews; + + const CustomTabBar({ + super.key, + required this.tabTitles, + required this.tabViews, + }); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: tabTitles.length, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Material( + elevation: 0, + color: Colors.white, + borderOnForeground: false, + child: TabBar( + isScrollable: true, + tabAlignment: TabAlignment.start, + labelColor: AppColor.primary, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + dividerColor: AppColor.border, + unselectedLabelColor: AppColor.primary, + indicatorSize: TabBarIndicatorSize.label, + indicatorWeight: 4, + indicatorColor: AppColor.primary, + tabs: tabTitles + .map( + (title) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Tab(text: title), + ), + ) + .toList(), + ), + ), + Expanded( + // ✅ ini bagian penting + child: TabBarView(children: tabViews), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_form.dart b/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_form.dart new file mode 100644 index 0000000..35dd931 --- /dev/null +++ b/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_form.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../../common/data/printer_data.dart'; +import '../../../../../../../common/theme/theme.dart'; +import '../../../../../../components/button/button.dart'; +import '../../../../../../components/dialog/dialog.dart'; +import '../../../../../../components/field/field.dart'; +import '../../../../../../components/spaces/space.dart'; + +class SettingPrinterForm extends StatelessWidget { + final String code; + const SettingPrinterForm({super.key, required this.code}); + + @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( + 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', + ), + ), + SpaceHeight(12), + AppTextFormField(label: 'Nama Printer'), + SpaceHeight(12), + AppDropdownSearch( + label: 'Kertas', + hintText: 'Kertas Tipe', + items: paperTypes, + itemAsString: (value) => "$value mm", + ), + SpaceHeight(20), + AppElevatedButton.filled(onPressed: () {}, label: 'Simpan'), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_receipt.dart b/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_receipt.dart new file mode 100644 index 0000000..9964c92 --- /dev/null +++ b/lib/presentation/pages/main/pages/setting/sections/printer/setting_printer_receipt.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../../common/theme/theme.dart'; +import 'setting_printer_form.dart'; + +class SettingPrinterReceipt extends StatelessWidget { + const SettingPrinterReceipt({super.key}); + + @override + Widget build(BuildContext context) { + return Material( + color: AppColor.background, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column(children: [SettingPrinterForm(code: 'receipt')]), + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/setting/sections/setting_printer_section.dart b/lib/presentation/pages/main/pages/setting/sections/setting_printer_section.dart new file mode 100644 index 0000000..77341a2 --- /dev/null +++ b/lib/presentation/pages/main/pages/setting/sections/setting_printer_section.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +import '../../../../../components/page/page_title.dart'; +import '../../../../../components/tab/custom_tabbar.dart'; +import 'printer/setting_printer_receipt.dart'; + +class SettingPrinterSection extends StatelessWidget { + const SettingPrinterSection({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + PageTitle( + isBack: false, + title: 'Printer', + subtitle: 'Kelola printer untuk cetak struk', + ), + Expanded( + child: CustomTabBar( + tabTitles: [ + 'Receipt Printer', + 'Checker Printer', + 'Kitchen Printer', + 'Bar Printer', + 'Tiket Printer', + ], + tabViews: [ + SettingPrinterReceipt(), + Text('Checker Printer'), + Text('Kitchen Printer'), + Text('Bar Printer'), + Text('Tiket Printer'), + ], + ), + ), + ], + ); + } +} diff --git a/lib/presentation/pages/main/pages/setting/setting_page.dart b/lib/presentation/pages/main/pages/setting/setting_page.dart index be9d865..0d5ceff 100644 --- a/lib/presentation/pages/main/pages/setting/setting_page.dart +++ b/lib/presentation/pages/main/pages/setting/setting_page.dart @@ -1,12 +1,42 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../application/printer/printer_bloc.dart'; +import '../../../../../common/theme/theme.dart'; +import '../../../../../injection.dart'; +import 'sections/setting_printer_section.dart'; +import 'widgets/setting_left_panel.dart'; @RoutePage() -class SettingPage extends StatelessWidget { +class SettingPage extends StatelessWidget implements AutoRouteWrapper { const SettingPage({super.key}); @override Widget build(BuildContext context) { - return const Center(child: Text('Setting Page')); + return Scaffold( + backgroundColor: AppColor.white, + body: BlocBuilder( + builder: (context, state) { + return Row( + children: [ + Expanded(flex: 2, child: SettingLeftPanel(state: state)), + Expanded( + flex: 4, + child: switch (state.index) { + 0 => SettingPrinterSection(), + 1 => Container(), + _ => Container(), + }, + ), + ], + ); + }, + ), + ); } + + @override + Widget wrappedRoute(BuildContext context) => + BlocProvider(create: (context) => getIt(), child: this); } diff --git a/lib/presentation/pages/main/pages/setting/widgets/setting_left_panel.dart b/lib/presentation/pages/main/pages/setting/widgets/setting_left_panel.dart new file mode 100644 index 0000000..e8da79a --- /dev/null +++ b/lib/presentation/pages/main/pages/setting/widgets/setting_left_panel.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../../../application/printer/printer_bloc.dart'; +import '../../../../../../common/theme/theme.dart'; +import '../../../../../components/page/page_title.dart'; +import 'setting_tile.dart'; + +class SettingLeftPanel extends StatelessWidget { + final PrinterState state; + const SettingLeftPanel({super.key, required this.state}); + + @override + Widget build(BuildContext context) { + return Material( + color: AppColor.white, + child: Column( + children: [ + PageTitle( + title: 'Pengaturan', + subtitle: 'Kelola pengaturan aplikasi', + isBack: false, + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + SettingTile( + index: 0, + currentIndex: state.index, + title: 'Printer', + subtitle: 'Kelola printer', + icon: Icons.print_outlined, + onTap: () => context.read().add( + PrinterEvent.indexChanged(0), + ), + ), + SettingTile( + index: 1, + currentIndex: state.index, + title: 'Sinkronisasi', + subtitle: 'Sinkronisasi data', + icon: Icons.sync_outlined, + onTap: () => context.read().add( + PrinterEvent.indexChanged(1), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/main/pages/setting/widgets/setting_tile.dart b/lib/presentation/pages/main/pages/setting/widgets/setting_tile.dart new file mode 100644 index 0000000..0b7e49b --- /dev/null +++ b/lib/presentation/pages/main/pages/setting/widgets/setting_tile.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + +import '../../../../../../common/theme/theme.dart'; +import '../../../../../components/spaces/space.dart'; + +class SettingTile extends StatelessWidget { + final int index; + final int currentIndex; + final String title; + final String subtitle; + final IconData icon; + final Function() onTap; + + const SettingTile({ + super.key, + required this.title, + required this.subtitle, + required this.icon, + required this.onTap, + required this.index, + required this.currentIndex, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(12.0), + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: currentIndex == index + ? AppColor.primary + : Colors.transparent, + width: 4.0, + ), + ), + ), + child: Row( + children: [ + Icon( + icon, + size: 24.0, + color: currentIndex == index + ? AppColor.primary + : AppColor.textSecondary, + ), + const SpaceWidth(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: AppStyle.lg.copyWith(fontWeight: FontWeight.bold), + ), + SpaceHeight(4.0), + Text( + subtitle, + style: AppStyle.md.copyWith(color: AppColor.textSecondary), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 51a4c55..2ff9b5a 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -434,7 +434,7 @@ class SettingRoute extends _i22.PageRouteInfo { static _i22.PageInfo page = _i22.PageInfo( name, builder: (data) { - return const _i13.SettingPage(); + return _i22.WrappedRoute(child: const _i13.SettingPage()); }, ); } diff --git a/pubspec.lock b/pubspec.lock index 36367cf..9e83ebc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -840,6 +840,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.dev" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.dev" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" petitparser: dependency: transitive description: @@ -880,6 +928,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + print_bluetooth_thermal: + dependency: "direct main" + description: + name: print_bluetooth_thermal + sha256: "302adf937af81374eccad5e0e6eae548f6abfa5bc1649a7d8cd3051a9792d739" + url: "https://pub.dev" + source: hosted + version: "1.1.7" provider: dependency: transitive description: @@ -1253,6 +1309,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + win_ble: + dependency: transitive + description: + name: win_ble + sha256: "2a867e13c4b355b101fc2c6e2ac85eeebf965db34eca46856f8b478e93b41e96" + url: "https://pub.dev" + source: hosted + version: "1.1.1" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2435083..d419dc7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,8 @@ dependencies: dropdown_search: ^5.0.6 syncfusion_flutter_datepicker: ^31.2.3 fl_chart: ^1.1.1 + permission_handler: ^12.0.1 + print_bluetooth_thermal: ^1.1.7 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 7bfdda0..8c5477b 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,10 +8,16 @@ #include #include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); FirebaseCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + PrintBluetoothThermalPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PrintBluetoothThermalPluginCApi")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 0e68a11..a9ec512 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,8 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus firebase_core + permission_handler_windows + print_bluetooth_thermal ) list(APPEND FLUTTER_FFI_PLUGIN_LIST