Feat: fix boarding pass after scan QR
This commit is contained in:
parent
00083b6e75
commit
2e38c7da39
|
@ -680,7 +680,7 @@
|
||||||
"languageVersion": "3.4"
|
"languageVersion": "3.4"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generated": "2025-05-16T07:28:56.076101Z",
|
"generated": "2025-05-18T15:09:53.002617Z",
|
||||||
"generator": "pub",
|
"generator": "pub",
|
||||||
"generatorVersion": "3.5.0",
|
"generatorVersion": "3.5.0",
|
||||||
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
||||||
|
|
|
@ -132,7 +132,6 @@ class HistoryRepositoryImpl implements HistoryRepository {
|
||||||
data['id'] = transactionId;
|
data['id'] = transactionId;
|
||||||
data['ticketId'] = ticketId;
|
data['ticketId'] = ticketId;
|
||||||
|
|
||||||
// Konversi ke model TransactionModel
|
|
||||||
final transaction = TransactionModel.fromJson(data);
|
final transaction = TransactionModel.fromJson(data);
|
||||||
log('HistoryRepositoryImpl: berhasil mengambil transaksi dari Firestore');
|
log('HistoryRepositoryImpl: berhasil mengambil transaksi dari Firestore');
|
||||||
return transaction;
|
return transaction;
|
||||||
|
@ -141,4 +140,53 @@ class HistoryRepositoryImpl implements HistoryRepository {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> checkIfTicketHasPorter(String ticketId, String transactionId) async {
|
||||||
|
try {
|
||||||
|
log('HistoryRepositoryImpl: checking if ticket has porter, ticketId: $ticketId, transactionId: $transactionId');
|
||||||
|
|
||||||
|
final querySnapshot = await _firestore
|
||||||
|
.collection('porterTransactions')
|
||||||
|
.where('ticketId', isEqualTo: ticketId)
|
||||||
|
.where('transactionId', isEqualTo: transactionId)
|
||||||
|
.limit(1)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
final hasPorter = querySnapshot.docs.isNotEmpty;
|
||||||
|
log('HistoryRepositoryImpl: ticket has porter: $hasPorter');
|
||||||
|
|
||||||
|
return hasPorter;
|
||||||
|
} catch (e) {
|
||||||
|
log('HistoryRepositoryImpl: error checking if ticket has porter: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getPorterTransactionId(String ticketId, String transactionId) async {
|
||||||
|
try {
|
||||||
|
log('HistoryRepositoryImpl: getting porter transaction ID, ticketId: $ticketId, transactionId: $transactionId');
|
||||||
|
|
||||||
|
final querySnapshot = await _firestore
|
||||||
|
.collection('porterTransactions')
|
||||||
|
.where('ticketId', isEqualTo: ticketId)
|
||||||
|
.where('transactionId', isEqualTo: transactionId)
|
||||||
|
.limit(1)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (querySnapshot.docs.isEmpty) {
|
||||||
|
log('HistoryRepositoryImpl: no porter transaction found');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final porterTransactionId = querySnapshot.docs.first.id;
|
||||||
|
log('HistoryRepositoryImpl: found porter transaction ID: $porterTransactionId');
|
||||||
|
|
||||||
|
return porterTransactionId;
|
||||||
|
} catch (e) {
|
||||||
|
log('HistoryRepositoryImpl: error getting porter transaction ID: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:e_porter/domain/repositories/statistic_repository.dart';
|
import 'package:e_porter/domain/repositories/statistic_repository.dart';
|
||||||
|
|
||||||
class StatisticRepositoryImpl implements StatisticRepository {
|
class StatisticRepositoryImpl implements StatisticRepository {
|
||||||
|
|
|
@ -4,4 +4,6 @@ abstract class HistoryRepository {
|
||||||
Stream<List<TransactionModel>> getTransactionsStream(String userId, String status);
|
Stream<List<TransactionModel>> getTransactionsStream(String userId, String status);
|
||||||
Stream<List<TransactionModel>> getHistoryTransactionsStream(String userId);
|
Stream<List<TransactionModel>> getHistoryTransactionsStream(String userId);
|
||||||
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId);
|
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId);
|
||||||
|
Future<bool> checkIfTicketHasPorter(String ticketId, String transactionId);
|
||||||
|
Future<String?> getPorterTransactionId(String ticketId, String transactionId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,4 +22,12 @@ class HistoryUseCase {
|
||||||
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId) {
|
Future<TransactionModel?> getTransactionFromFirestore(String ticketId, String transactionId) {
|
||||||
return _repository.getTransactionFromFirestore(ticketId, transactionId);
|
return _repository.getTransactionFromFirestore(ticketId, transactionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> checkIfTicketHasPorter(String ticketId, String transactionId) {
|
||||||
|
return _repository.checkIfTicketHasPorter(ticketId, transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getPorterTransactionId(String ticketId, String transactionId) {
|
||||||
|
return _repository.getPorterTransactionId(ticketId, transactionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ class HistoryController extends GetxController {
|
||||||
|
|
||||||
final RxBool isLoading = true.obs;
|
final RxBool isLoading = true.obs;
|
||||||
final RxBool isCheckingExpiry = false.obs;
|
final RxBool isCheckingExpiry = false.obs;
|
||||||
|
final RxBool hasAssignedPorter = false.obs;
|
||||||
final RxString errorMessage = ''.obs;
|
final RxString errorMessage = ''.obs;
|
||||||
|
|
||||||
StreamSubscription? _pendingSubscription;
|
StreamSubscription? _pendingSubscription;
|
||||||
|
@ -22,6 +23,7 @@ class HistoryController extends GetxController {
|
||||||
StreamSubscription? _historySubscription;
|
StreamSubscription? _historySubscription;
|
||||||
|
|
||||||
String _currentUserId = '';
|
String _currentUserId = '';
|
||||||
|
String? currentPorterTransactionId;
|
||||||
|
|
||||||
HistoryController(this._historyUseCase);
|
HistoryController(this._historyUseCase);
|
||||||
|
|
||||||
|
@ -44,18 +46,6 @@ class HistoryController extends GetxController {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// _activeSubscription = _historyUseCase.getActiveTransactionsStream(userId).listen((transactions) {
|
|
||||||
// log('HistoryController: active transactions updated, count: ${transactions.length}');
|
|
||||||
|
|
||||||
// final sortedTransactions = _sortTransactionsByCreatedAt(transactions);
|
|
||||||
// activeTransactions.value = sortedTransactions;
|
|
||||||
|
|
||||||
// isLoading.value = false;
|
|
||||||
// }, onError: (error) {
|
|
||||||
// log('HistoryController: Error mendapatkan transaksi active: $error');
|
|
||||||
// isLoading.value = false;
|
|
||||||
// });
|
|
||||||
|
|
||||||
_activeSubscription = _historyUseCase.getActiveTransactionsStream(userId).map((list) {
|
_activeSubscription = _historyUseCase.getActiveTransactionsStream(userId).map((list) {
|
||||||
final todayStart = DateTime(
|
final todayStart = DateTime(
|
||||||
DateTime.now().year,
|
DateTime.now().year,
|
||||||
|
@ -126,6 +116,12 @@ class HistoryController extends GetxController {
|
||||||
|
|
||||||
if (transaction != null) {
|
if (transaction != null) {
|
||||||
selectedTransaction.value = transaction;
|
selectedTransaction.value = transaction;
|
||||||
|
|
||||||
|
hasAssignedPorter.value = await _historyUseCase.checkIfTicketHasPorter(ticketId, transactionId);
|
||||||
|
|
||||||
|
if (hasAssignedPorter.value) {
|
||||||
|
currentPorterTransactionId = await _historyUseCase.getPorterTransactionId(ticketId, transactionId);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMessage.value = 'Transaksi tidak ditemukan';
|
errorMessage.value = 'Transaksi tidak ditemukan';
|
||||||
}
|
}
|
||||||
|
@ -140,6 +136,26 @@ class HistoryController extends GetxController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> checkIfTicketHasPorter(String ticketId, String transactionId) async {
|
||||||
|
try {
|
||||||
|
hasAssignedPorter.value = await _historyUseCase.checkIfTicketHasPorter(ticketId, transactionId);
|
||||||
|
return hasAssignedPorter.value;
|
||||||
|
} catch (e) {
|
||||||
|
log('HistoryController: error checking if ticket has porter: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getPorterTransactionId(String ticketId, String transactionId) async {
|
||||||
|
try {
|
||||||
|
currentPorterTransactionId = await _historyUseCase.getPorterTransactionId(ticketId, transactionId);
|
||||||
|
return currentPorterTransactionId;
|
||||||
|
} catch (e) {
|
||||||
|
log('HistoryController: error getting porter transaction ID: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> refreshTransactions(String userId) async {
|
Future<void> refreshTransactions(String userId) async {
|
||||||
log('HistoryController: Memperbarui data transaksi untuk userId: $userId');
|
log('HistoryController: Memperbarui data transaksi untuk userId: $userId');
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:e_porter/_core/component/button/button_outline.dart';
|
import 'package:e_porter/_core/component/button/button_outline.dart';
|
||||||
import 'package:e_porter/_core/component/dotted/dashed_line_component.dart';
|
import 'package:e_porter/_core/component/dotted/dashed_line_component.dart';
|
||||||
import 'package:e_porter/_core/constants/colors.dart';
|
import 'package:e_porter/_core/constants/colors.dart';
|
||||||
|
@ -14,7 +13,6 @@ import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:zoom_tap_animation/zoom_tap_animation.dart';
|
import 'package:zoom_tap_animation/zoom_tap_animation.dart';
|
||||||
|
|
||||||
import '../../../../_core/component/appbar/appbar_component.dart';
|
import '../../../../_core/component/appbar/appbar_component.dart';
|
||||||
import '../../../../_core/component/button/button_fill.dart';
|
import '../../../../_core/component/button/button_fill.dart';
|
||||||
import '../../../../_core/component/card/custome_shadow_cotainner.dart';
|
import '../../../../_core/component/card/custome_shadow_cotainner.dart';
|
||||||
|
@ -41,7 +39,6 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
|
||||||
final args = Get.arguments as Map<String, dynamic>;
|
final args = Get.arguments as Map<String, dynamic>;
|
||||||
id_transaction = args['id_transaction'];
|
id_transaction = args['id_transaction'];
|
||||||
id_ticket = args['id_ticket'];
|
id_ticket = args['id_ticket'];
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_loadTransactionData();
|
_loadTransactionData();
|
||||||
});
|
});
|
||||||
|
@ -100,13 +97,17 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TypographyStyles.caption(
|
_buildUIHeader(
|
||||||
"Kode Booking Maskapai",
|
value: "${transaction.idBooking}",
|
||||||
color: GrayColors.gray500,
|
onTap: () {
|
||||||
fontWeight: FontWeight.w500,
|
Get.toNamed(
|
||||||
|
Routes.PROCESSING,
|
||||||
|
arguments: {
|
||||||
|
'transactionPorterId': historyController.currentPorterTransactionId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 6.h),
|
|
||||||
TypographyStyles.body("${transaction.idBooking}", color: GrayColors.gray800),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 20.h),
|
padding: EdgeInsets.symmetric(vertical: 20.h),
|
||||||
child: CustomDashedLine(),
|
child: CustomDashedLine(),
|
||||||
|
@ -123,15 +124,7 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
|
||||||
),
|
),
|
||||||
SizedBox(height: 4.h),
|
SizedBox(height: 4.h),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: _buildServiceInfoItems(transaction),
|
||||||
TypographyStyles.small("Fast Track (FT)",
|
|
||||||
color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
|
|
||||||
SizedBox(width: 10.w),
|
|
||||||
TypographyStyles.small("${transaction.flightDetails['flightClass']}",
|
|
||||||
color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
Row(
|
Row(
|
||||||
|
@ -250,30 +243,36 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ButtonOutline(
|
if (!historyController.hasAssignedPorter.value)
|
||||||
text: 'Scan QR Code Porter',
|
ButtonOutline(
|
||||||
textColor: PrimaryColors.primary800,
|
text: 'Scan QR Code Porter',
|
||||||
onTap: () async {
|
textColor: PrimaryColors.primary800,
|
||||||
await Permission.camera.status;
|
onTap: () async {
|
||||||
|
await Permission.camera.status;
|
||||||
|
|
||||||
final result = await Get.toNamed(
|
final result = await Get.toNamed(
|
||||||
Routes.SCANQR,
|
Routes.SCANQR,
|
||||||
arguments: {
|
arguments: {
|
||||||
'ticketId': transaction.ticketId,
|
'ticketId': transaction.ticketId,
|
||||||
'transactionId': transaction.id,
|
'transactionId': transaction.id,
|
||||||
},
|
},
|
||||||
);
|
|
||||||
|
|
||||||
if (result == 'PORTER_BUSY') {
|
|
||||||
SnackbarHelper.showError(
|
|
||||||
'Porter Tidak Tersedia',
|
|
||||||
'Tidak ada porter yang tersedia atau semua porter sedang sibuk, coba nanti.',
|
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
if (result == 'PORTER_BUSY') {
|
||||||
},
|
SnackbarHelper.showError(
|
||||||
),
|
'Porter Tidak Tersedia',
|
||||||
SizedBox(height: 12.h),
|
'Tidak ada porter yang tersedia atau semua porter sedang sibuk, coba nanti.',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == 'SUCCESS') {
|
||||||
|
historyController.hasAssignedPorter.value = true;
|
||||||
|
_loadTransactionData();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (!historyController.hasAssignedPorter.value) SizedBox(height: 12.h),
|
||||||
ButtonFill(
|
ButtonFill(
|
||||||
text: 'Cetak Boarding Pass',
|
text: 'Cetak Boarding Pass',
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
|
@ -297,6 +296,45 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildUIHeader({required String value, required VoidCallback? onTap}) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TypographyStyles.caption(
|
||||||
|
"Kode Booking Maskapai",
|
||||||
|
color: GrayColors.gray500,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
SizedBox(height: 6.h),
|
||||||
|
TypographyStyles.body(value, color: GrayColors.gray800),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
if (historyController.hasAssignedPorter.value) {
|
||||||
|
return ZoomTapAnimation(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.shade400,
|
||||||
|
borderRadius: BorderRadius.circular(20.r),
|
||||||
|
),
|
||||||
|
child: TypographyStyles.small("Lihat Status Porter", color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return SizedBox();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildDataBandara({
|
Widget _buildDataBandara({
|
||||||
required String titleDeparture,
|
required String titleDeparture,
|
||||||
required String planeDeparture,
|
required String planeDeparture,
|
||||||
|
@ -342,6 +380,48 @@ class _DetailTicketScreenState extends State<DetailTicketScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildServiceInfoItems(TransactionModel transaction) {
|
||||||
|
List<Widget> items = [];
|
||||||
|
if (transaction.porterServiceDetails != null &&
|
||||||
|
(transaction.porterServiceDetails as Map<String, dynamic>).isNotEmpty) {
|
||||||
|
if ((transaction.porterServiceDetails as Map<String, dynamic>).containsKey('departure') &&
|
||||||
|
(transaction.porterServiceDetails as Map<String, dynamic>)['departure'] != null) {
|
||||||
|
final departureName =
|
||||||
|
(transaction.porterServiceDetails as Map<String, dynamic>)['departure']['name'] as String? ?? "Fast Track";
|
||||||
|
items.add(TypographyStyles.small(
|
||||||
|
departureName,
|
||||||
|
color: GrayColors.gray600,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
));
|
||||||
|
items.add(SizedBox(width: 10.w));
|
||||||
|
items.add(CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)));
|
||||||
|
items.add(SizedBox(width: 10.w));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((transaction.porterServiceDetails as Map<String, dynamic>).containsKey('transit') &&
|
||||||
|
(transaction.porterServiceDetails as Map<String, dynamic>)['transit'] != null) {
|
||||||
|
final transitName =
|
||||||
|
(transaction.porterServiceDetails as Map<String, dynamic>)['transit']['name'] as String? ?? "Transit";
|
||||||
|
items.add(TypographyStyles.small(
|
||||||
|
transitName,
|
||||||
|
color: GrayColors.gray600,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
));
|
||||||
|
items.add(SizedBox(width: 10.w));
|
||||||
|
items.add(CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)));
|
||||||
|
items.add(SizedBox(width: 10.w));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(TypographyStyles.small(
|
||||||
|
"${transaction.flightDetails['flightClass']}",
|
||||||
|
color: GrayColors.gray600,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
));
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
void _showCetakBoardingPassBottomSheet(TransactionModel transaction) {
|
void _showCetakBoardingPassBottomSheet(TransactionModel transaction) {
|
||||||
Get.bottomSheet(
|
Get.bottomSheet(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
|
|
|
@ -28,6 +28,7 @@ class _ScanQRScreenState extends State<ScanQRScreen> {
|
||||||
torchEnabled: false,
|
torchEnabled: false,
|
||||||
returnImage: true,
|
returnImage: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
bool isProcessing = false;
|
bool isProcessing = false;
|
||||||
bool _isTorchOn = false;
|
bool _isTorchOn = false;
|
||||||
|
|
||||||
|
@ -220,7 +221,7 @@ class _ScanQRScreenState extends State<ScanQRScreen> {
|
||||||
transactionId: transactionId,
|
transactionId: transactionId,
|
||||||
location: rawLocation,
|
location: rawLocation,
|
||||||
);
|
);
|
||||||
await Future.delayed(Duration(seconds: 3));
|
// await Future.delayed(Duration(seconds: 3));
|
||||||
|
|
||||||
log('[ScanQRScreen] requestPorter succeeded: $result');
|
log('[ScanQRScreen] requestPorter succeeded: $result');
|
||||||
|
|
||||||
|
@ -245,7 +246,6 @@ class _ScanQRScreenState extends State<ScanQRScreen> {
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
final msg = e.toString();
|
final msg = e.toString();
|
||||||
if (msg.contains('Porter tidak tersedia')) {
|
if (msg.contains('Porter tidak tersedia')) {
|
||||||
// Porter sibuk → langsung pop dengan result
|
|
||||||
Get.back(result: 'PORTER_BUSY');
|
Get.back(result: 'PORTER_BUSY');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,12 +34,6 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// final args = Get.arguments as Map<String, dynamic>;
|
|
||||||
// final location = args['location'] ?? '';
|
|
||||||
// final ticketId = args['ticketId'] ?? '';
|
|
||||||
// final transactionId = args['transactionId'];
|
|
||||||
// final porterOnlineId = args['porterOnlineId'];
|
|
||||||
// final transactionPorterId = args['transactionPorterId'] ?? '';
|
|
||||||
_initializeData();
|
_initializeData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +53,6 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
|
|
||||||
log('Memulai pemantauan transaksi porter: $transactionPorterId');
|
log('Memulai pemantauan transaksi porter: $transactionPorterId');
|
||||||
|
|
||||||
// Dapatkan detail transaksi dan mulai memantau perubahan
|
|
||||||
_porterController.getTransactionById(transactionPorterId).then((transaction) {
|
_porterController.getTransactionById(transactionPorterId).then((transaction) {
|
||||||
if (transaction == null) {
|
if (transaction == null) {
|
||||||
log('Transaksi tidak ditemukan: $transactionPorterId');
|
log('Transaksi tidak ditemukan: $transactionPorterId');
|
||||||
|
@ -70,7 +63,6 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
log('Error mendapatkan transaksi: $e');
|
log('Error mendapatkan transaksi: $e');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mulai memantau transaksi secara real-time
|
|
||||||
_porterController.watchTransaction(transactionPorterId);
|
_porterController.watchTransaction(transactionPorterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,80 +78,48 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
Get.back();
|
Get.back();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: SafeArea(child: Obx(
|
body: SafeArea(
|
||||||
() {
|
child: Obx(
|
||||||
final transaction = _porterController.currentTransaction.value;
|
() {
|
||||||
final isLoading = _porterController.isLoading.value;
|
final transaction = _porterController.currentTransaction.value;
|
||||||
final error = _porterController.error.value;
|
final isLoading = _porterController.isLoading.value;
|
||||||
|
final error = _porterController.error.value;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (error.isNotEmpty) {
|
if (error.isNotEmpty) {
|
||||||
return Center(
|
return Center(child: TypographyStyles.body('Data transaksi tidak ditemukan', color: GrayColors.gray400));
|
||||||
child: Column(
|
}
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
if (transaction == null) {
|
||||||
children: [
|
return Center(
|
||||||
Icon(Icons.error_outline, size: 48.h, color: Colors.red),
|
child: Column(
|
||||||
SizedBox(height: 16.h),
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
TypographyStyles.body(
|
children: [
|
||||||
'Terjadi Kesalahan',
|
Icon(Icons.search_off, size: 48.h, color: GrayColors.gray400),
|
||||||
color: Colors.red,
|
SizedBox(height: 16.h),
|
||||||
),
|
TypographyStyles.body('Transaksi tidak ditemukan', color: GrayColors.gray600),
|
||||||
SizedBox(height: 8.h),
|
],
|
||||||
Padding(
|
),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 32.w),
|
);
|
||||||
child: TypographyStyles.caption(
|
}
|
||||||
error,
|
return Padding(
|
||||||
color: GrayColors.gray600,
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
||||||
textAlign: TextAlign.center,
|
child: SingleChildScrollView(
|
||||||
),
|
child: Column(
|
||||||
),
|
children: [
|
||||||
SizedBox(height: 16.h),
|
_buildPorterStatusCard(transaction.status),
|
||||||
ButtonFill(
|
SizedBox(height: 20.h),
|
||||||
text: 'Kembali',
|
_buildInfoPorterCard(kodePorter: transaction.kodePorter, namePorter: transaction.porterUserId),
|
||||||
textColor: Colors.white,
|
SizedBox(height: 20.h),
|
||||||
onTap: () => Get.back(),
|
_buildPorterDetailsCard(transaction),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
if (transaction == null) {
|
),
|
||||||
return Center(
|
),
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.search_off, size: 48.h, color: GrayColors.gray400),
|
|
||||||
SizedBox(height: 16.h),
|
|
||||||
TypographyStyles.body(
|
|
||||||
'Transaksi tidak ditemukan',
|
|
||||||
color: GrayColors.gray600,
|
|
||||||
),
|
|
||||||
SizedBox(height: 16.h),
|
|
||||||
ButtonFill(
|
|
||||||
text: 'Kembali',
|
|
||||||
textColor: Colors.white,
|
|
||||||
onTap: () => Get.back(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
_buildPorterStatusCard(transaction.status),
|
|
||||||
SizedBox(height: 20.h),
|
|
||||||
_buildPorterDetailsCard(transaction),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
bottomNavigationBar: CustomeShadowCotainner(
|
bottomNavigationBar: CustomeShadowCotainner(
|
||||||
child: ButtonFill(
|
child: ButtonFill(
|
||||||
text: 'Kembali ke menu',
|
text: 'Kembali ke menu',
|
||||||
|
@ -170,59 +130,6 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDesignOld() {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TypographyStyles.h1('Ilustrasi'),
|
|
||||||
SizedBox(height: 32.h),
|
|
||||||
CustomeShadowCotainner(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TypographyStyles.h6('Tunggu Portermu', color: GrayColors.gray800),
|
|
||||||
SizedBox(height: 4.h),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
TypographyStyles.caption(
|
|
||||||
'Tunngu dari pihak Porter merespon',
|
|
||||||
color: GrayColors.gray500,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
SizedBox(width: 4.w),
|
|
||||||
Icon(Icons.timelapse_outlined)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 20.h),
|
|
||||||
CustomeShadowCotainner(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
TypographyStyles.body('Ahmad Choirul Umam', color: GrayColors.gray800),
|
|
||||||
SizedBox(height: 10.h),
|
|
||||||
Divider(thickness: 1, color: GrayColors.gray200),
|
|
||||||
SizedBox(height: 10.h),
|
|
||||||
TypographyStyles.body('Lokasi', color: GrayColors.gray800),
|
|
||||||
SizedBox(height: 10.h),
|
|
||||||
// _buildRowLocation(location: 'Gate Penerbangan', desc: 'Lokasi Anda'),
|
|
||||||
SizedBox(height: 10.h),
|
|
||||||
// _buildRowLocation(location: 'Guyangan', desc: 'Lokasi Porter Anda'),
|
|
||||||
SizedBox(height: 10.h),
|
|
||||||
// _buildRowLocation(location: 'Porter menuju ke lokasi anda', desc: 'Porter bergerak'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildPorterStatusCard(String status) {
|
Widget _buildPorterStatusCard(String status) {
|
||||||
String statusText = '';
|
String statusText = '';
|
||||||
Widget statusIcon = const SizedBox.shrink();
|
Widget statusIcon = const SizedBox.shrink();
|
||||||
|
@ -280,6 +187,39 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoPorterCard({required String kodePorter, required String namePorter}) {
|
||||||
|
return CustomeShadowCotainner(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TypographyStyles.h6('Informasi Porter', color: GrayColors.gray800),
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TypographyStyles.body('Kode Porter', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
|
TypographyStyles.body('Nama Porter', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(width: 20.w),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
TypographyStyles.body(kodePorter, color: GrayColors.gray800, maxlines: 1),
|
||||||
|
TypographyStyles.body(namePorter, color: GrayColors.gray800, maxlines: 1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildPorterDetailsCard(dynamic transaction) {
|
Widget _buildPorterDetailsCard(dynamic transaction) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
@ -287,11 +227,7 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TypographyStyles.body('Informasi Porter', color: GrayColors.gray800),
|
TypographyStyles.h6('Lokasi', color: GrayColors.gray800),
|
||||||
SizedBox(height: 10.h),
|
|
||||||
Divider(thickness: 1, color: GrayColors.gray200),
|
|
||||||
SizedBox(height: 10.h),
|
|
||||||
TypographyStyles.body('Lokasi', color: GrayColors.gray800),
|
|
||||||
SizedBox(height: 10.h),
|
SizedBox(height: 10.h),
|
||||||
_buildRowLocation(
|
_buildRowLocation(
|
||||||
location: location.isNotEmpty ? location : transaction.locationPassenger,
|
location: location.isNotEmpty ? location : transaction.locationPassenger,
|
||||||
|
@ -310,20 +246,6 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
desc: 'Status Porter',
|
desc: 'Status Porter',
|
||||||
timestamp: now,
|
timestamp: now,
|
||||||
),
|
),
|
||||||
SizedBox(height: 10.h),
|
|
||||||
TypographyStyles.body('Kode Porter', color: GrayColors.gray800),
|
|
||||||
SizedBox(height: 4.h),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: PrimaryColors.primary100,
|
|
||||||
borderRadius: BorderRadius.circular(4.r),
|
|
||||||
),
|
|
||||||
child: TypographyStyles.h6(
|
|
||||||
transaction.kodePorter,
|
|
||||||
color: PrimaryColors.primary800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -353,12 +275,12 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
TypographyStyles.caption(
|
TypographyStyles.body(
|
||||||
_dateFormat.format(timestamp),
|
_dateFormat.format(timestamp),
|
||||||
color: GrayColors.gray600,
|
color: GrayColors.gray600,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
),
|
),
|
||||||
TypographyStyles.caption(
|
TypographyStyles.body(
|
||||||
_timeFormat.format(timestamp),
|
_timeFormat.format(timestamp),
|
||||||
color: GrayColors.gray600,
|
color: GrayColors.gray600,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
|
@ -370,16 +292,8 @@ class _ProcessingPorterScreenState extends State<ProcessingPorterScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
TypographyStyles.caption(
|
TypographyStyles.body(location, color: GrayColors.gray800),
|
||||||
location,
|
TypographyStyles.caption(desc, color: GrayColors.gray600, fontWeight: FontWeight.w400)
|
||||||
color: GrayColors.gray800,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
TypographyStyles.small(
|
|
||||||
desc,
|
|
||||||
color: GrayColors.gray600,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -409,7 +409,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
icon: Icon(Icons.calendar_today, size: 20.r, color: PrimaryColors.primary800)),
|
icon: Icon(Icons.calendar_today, size: 20.r, color: PrimaryColors.primary800)),
|
||||||
SizedBox(width: 8.w),
|
SizedBox(width: 8.w),
|
||||||
DateSetting(
|
DateSetting(
|
||||||
onTap: () => _statisticController.previousMonth(),
|
onTap: () => _statisticController.nextMonth(),
|
||||||
icon: Icon(Icons.chevron_right, size: 20.r, color: GrayColors.gray800),
|
icon: Icon(Icons.chevron_right, size: 20.r, color: GrayColors.gray800),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -489,9 +489,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
final hasPorter = _porterQueueController.currentPorter.value != null;
|
final hasPorter = _porterQueueController.currentPorter.value != null;
|
||||||
return ZoomTapAnimation(
|
return ZoomTapAnimation(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: hasPorter
|
onTap: hasPorter ? _handleStopPorterQueue : _handlePorterQueueCreation,
|
||||||
? _handleStopPorterQueue // sekarang benar-benar terpilih saat Obx rebuild
|
|
||||||
: _handlePorterQueueCreation,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
|
@ -538,6 +536,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
Future<bool> _showStopConfirmationDialog() async {
|
Future<bool> _showStopConfirmationDialog() async {
|
||||||
return await Get.dialog<bool>(
|
return await Get.dialog<bool>(
|
||||||
Dialog(
|
Dialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10.r),
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
),
|
),
|
||||||
|
@ -551,13 +550,12 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
color: GrayColors.gray800,
|
color: GrayColors.gray800,
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
Text(
|
TypographyStyles.caption(
|
||||||
'Apakah Anda yakin ingin menghentikan layanan porter?',
|
'Apakah Anda yakin ingin menghentikan layanan porter?',
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: GrayColors.gray500,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
maxlines: 3,
|
||||||
fontSize: 14.sp,
|
|
||||||
color: GrayColors.gray600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 24.h),
|
SizedBox(height: 24.h),
|
||||||
Row(
|
Row(
|
||||||
|
@ -569,7 +567,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: GrayColors.gray600,
|
foregroundColor: GrayColors.gray600,
|
||||||
),
|
),
|
||||||
child: Text('Batal'),
|
child: TypographyStyles.body('Batal', color: GrayColors.gray600),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 8.w),
|
SizedBox(width: 8.w),
|
||||||
|
@ -580,7 +578,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
backgroundColor: RedColors.red500,
|
backgroundColor: RedColors.red500,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
child: Text('Berhenti'),
|
child: TypographyStyles.body('Berhenti', color: Colors.white),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -681,7 +679,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
required String nameAvatar,
|
required String nameAvatar,
|
||||||
required String nameUser,
|
required String nameUser,
|
||||||
required String subTitle,
|
required String subTitle,
|
||||||
// VoidCallback? onTap,
|
|
||||||
}) {
|
}) {
|
||||||
return CustomeShadowCotainner(
|
return CustomeShadowCotainner(
|
||||||
sizeRadius: 0.r,
|
sizeRadius: 0.r,
|
||||||
|
@ -703,12 +700,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// ZoomTapAnimation(
|
|
||||||
// child: IconButton(
|
|
||||||
// onPressed: onTap,
|
|
||||||
// icon: CustomeIcons.NotificationOutline(),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue