Skip to content

Commit

Permalink
DEV-43005 (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
DCrow authored Oct 4, 2024
1 parent ac7a9a5 commit b787c5d
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 0 deletions.
13 changes: 13 additions & 0 deletions lib/app/pages/orders/orders_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import '/app/repositories/orders_repository.dart';
import '/app/repositories/users_repository.dart';
import '/app/widgets/widgets.dart';
import 'order/order_page.dart';
import 'orders_qr_scan/orders_qr_scan_page.dart';

part 'orders_state.dart';
part 'orders_view_model.dart';
Expand Down Expand Up @@ -100,6 +101,13 @@ class _OrdersViewState extends State<_OrdersView> {
).show();
}

Future<void> showOrdersQRScan() async {
Navigator.push(
context,
MaterialPageRoute(builder: (BuildContext context) => OrdersQRScanPagePage())
);
}

@override
Widget build(BuildContext context) {
return BlocConsumer<OrdersViewModel, OrdersState>(
Expand All @@ -108,6 +116,11 @@ class _OrdersViewState extends State<_OrdersView> {
appBar: AppBar(
title: const Text('Заказы'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.assignment_turned_in),
onPressed: showOrdersQRScan,
tooltip: 'Массовая выдача заказов'
),
IconButton(
icon: const Icon(Icons.qr_code_scanner),
onPressed: showQRScan,
Expand Down
127 changes: 127 additions & 0 deletions lib/app/pages/orders/orders_qr_scan/orders_qr_scan_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import 'dart:async';

import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:u_app_utils/u_app_utils.dart';

import '/app/constants/qr_types.dart';
import '/app/constants/strings.dart';
import '/app/constants/style.dart';
import '/app/data/database.dart';
import '/app/entities/entities.dart';
import '/app/pages/shared/page_view_model.dart';
import '/app/repositories/orders_repository.dart';

part 'orders_qr_scan_state.dart';
part 'orders_qr_scan_view_model.dart';

class OrdersQRScanPagePage extends StatelessWidget {
OrdersQRScanPagePage({
Key? key
}) : super(key: key);

@override
Widget build(BuildContext context) {
return BlocProvider<OrdersQRScanPageViewModel>(
create: (context) => OrdersQRScanPageViewModel(
RepositoryProvider.of<OrdersRepository>(context),
),
child: _OrdersQRScanPageView(),
);
}
}

class _OrdersQRScanPageView extends StatefulWidget {
@override
_OrdersQRScanPageViewState createState() => _OrdersQRScanPageViewState();
}

class _OrdersQRScanPageViewState extends State<_OrdersQRScanPageView> {
late final ProgressDialog _progressDialog = ProgressDialog(context: context);

@override
void dispose() {
_progressDialog.close();
super.dispose();
}

@override
Widget build(BuildContext context) {
return BlocConsumer<OrdersQRScanPageViewModel, OrdersQRScanPageState>(
builder: (context, state) {
OrdersQRScanPageViewModel vm = context.read<OrdersQRScanPageViewModel>();

return ScanView(
showScanner: true,
onRead: vm.readQRCode,
child: vm.state.orderScanned.isEmpty ? Container() : DraggableScrollableSheet(
initialChildSize: 0.33,
builder: (BuildContext context, scrollController) {
return Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Theme.of(context).canvasColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
),
child: ListView(
controller: scrollController,
children: [
ListTile(
trailing: TextButton(
onPressed: () {},
child: TextButton(
onPressed: vm.confirmOrders,
child: Text(
'Завершить',
style: Style.listTileTitleText.merge(const TextStyle(color: Colors.black))
)
)
)
),
...vm.state.orderScanned.entries.map((e) => _scannedOrderTile(context, e))
]
)
);
}
)
);
},
listener: (context, state) async {
switch (state.status) {
case OrdersQRScanPageStateStatus.inProgress:
await _progressDialog.open();
break;
case OrdersQRScanPageStateStatus.scanReadFailure:
Misc.showMessage(context, state.message);
break;
case OrdersQRScanPageStateStatus.scanReadFinished:
break;
case OrdersQRScanPageStateStatus.failure:
case OrdersQRScanPageStateStatus.success:
Misc.showMessage(context, state.message);
_progressDialog.close();
break;
default:
}
}
);
}

Widget _scannedOrderTile(BuildContext context, MapEntry<OrderEx, List<bool>> entry) {
OrdersQRScanPageViewModel vm = context.read<OrdersQRScanPageViewModel>();

return Dismissible(
key: Key(entry.hashCode.toString()),
background: Container(color: Colors.red[500]),
onDismissed: (direction) => vm.removeScannedOrder(entry.key),
child: ListTile(
title: Text('Заказ ${entry.key.order.trackingNumber}'),
trailing: Text('Мест ${entry.value.where((k) => k).length}/${entry.key.order.packages}'),
)
);
}
}
39 changes: 39 additions & 0 deletions lib/app/pages/orders/orders_qr_scan/orders_qr_scan_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
part of 'orders_qr_scan_page.dart';

enum OrdersQRScanPageStateStatus {
initial,
dataLoaded,
scanReadFailure,
scanReadFinished,
inProgress,
failure,
success
}

class OrdersQRScanPageState {
OrdersQRScanPageState({
this.status = OrdersQRScanPageStateStatus.initial,
this.orderExList = const [],
this.orderScanned = const {},
this.message = ''
});

final OrdersQRScanPageStateStatus status;
final List<OrderEx> orderExList;
final String message;
final Map<OrderEx, List<bool>> orderScanned;

OrdersQRScanPageState copyWith({
OrdersQRScanPageStateStatus? status,
Map<OrderEx, List<bool>>? orderScanned,
List<OrderEx>? orderExList,
String? message
}) {
return OrdersQRScanPageState(
status: status ?? this.status,
orderExList: orderExList ?? this.orderExList,
orderScanned: orderScanned ?? this.orderScanned,
message: message ?? this.message
);
}
}
107 changes: 107 additions & 0 deletions lib/app/pages/orders/orders_qr_scan/orders_qr_scan_view_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
part of 'orders_qr_scan_page.dart';

class OrdersQRScanPageViewModel extends PageViewModel<OrdersQRScanPageState, OrdersQRScanPageStateStatus> {
static const String qrType = 'ORDER';
final OrdersRepository ordersRepository;

StreamSubscription<List<OrderEx>>? orderExListSubscription;

OrdersQRScanPageViewModel(this.ordersRepository) : super(OrdersQRScanPageState());

@override
OrdersQRScanPageStateStatus get status => state.status;


@override
Future<void> initViewModel() async {
await super.initViewModel();

orderExListSubscription = ordersRepository.watchOrderExList()
.listen((event) {
emit(state.copyWith(status: OrdersQRScanPageStateStatus.dataLoaded, orderExList: event));
});
}

@override
Future<void> close() async {
await super.close();

await orderExListSubscription?.cancel();
}

Future<void> readQRCode(String? qrCode) async {
if (qrCode == null) return;

List<String> qrCodeData = qrCode.split(' ');
String version = qrCodeData[0];

if (version != Strings.qrCodeVersion) {
emit(state.copyWith(
status: OrdersQRScanPageStateStatus.scanReadFailure,
message: 'Считан не поддерживаемый QR код'
));
return;
}

if (qrCodeData[3] != QRType.order.typeName) {
emit(state.copyWith(status: OrdersQRScanPageStateStatus.scanReadFailure, message: 'QR код не от заказа'));
return;
}

return await _processQR(qrCodeData[4], int.parse(qrCodeData[5]));
}

Future<void> _processQR(String qrTrackingNumber, int packageNumber) async {
OrdersQRScanPageState newState = state;

OrderEx? orderEx = state.orderExList.firstWhereOrNull((e) => e.order.trackingNumber == qrTrackingNumber);

if (orderEx == null) {
emit(state.copyWith(status: OrdersQRScanPageStateStatus.scanReadFailure, message: 'Заказ не найден'));
return;
}

Map<OrderEx, List<bool>> newOrderScanned = Map.of(state.orderScanned);
List<bool> scannedPackages = newOrderScanned[orderEx] ?? List.generate(orderEx.order.packages, (_) => false);

if (scannedPackages[packageNumber - 1]) {
emit(state.copyWith(status: OrdersQRScanPageStateStatus.scanReadFailure, message: 'QR код уже был считан'));
return;
}

scannedPackages[packageNumber - 1] = true;
newOrderScanned[orderEx] = scannedPackages;
emit(newState.copyWith(orderScanned: newOrderScanned, status: OrdersQRScanPageStateStatus.scanReadFinished));
}

Future<void> confirmOrders() async {
emit(state.copyWith(status: OrdersQRScanPageStateStatus.inProgress));

final fullyScannedOrders = state.orderScanned.entries.where((e) => e.value.none((v) => !v));

if (fullyScannedOrders.isEmpty) {
emit(state.copyWith(status: OrdersQRScanPageStateStatus.failure, message: 'Нет заказов для выдачи'));
return;
}

for (var order in fullyScannedOrders) {
try {
await ordersRepository.confirmOrder(order.key);

removeScannedOrder(order.key);
} on AppError catch(e) {
emit(state.copyWith(status: OrdersQRScanPageStateStatus.failure, message: e.message));
return;
}
}

emit(state.copyWith(status: OrdersQRScanPageStateStatus.success, message: 'Заказы успешно выданы'));
}

void removeScannedOrder(OrderEx orderEx) {
Map<OrderEx, List<bool>> newOrderScanned = Map.of(state.orderScanned);
newOrderScanned.remove(orderEx);

emit(state.copyWith(orderScanned: newOrderScanned));
}
}

0 comments on commit b787c5d

Please sign in to comment.