Feat: done logic transaction with upload file

This commit is contained in:
orangdeso 2025-04-09 23:23:21 +07:00
parent ea3e1080e0
commit 7b40cbace4
12 changed files with 444 additions and 172 deletions

View File

@ -632,7 +632,7 @@
"languageVersion": "3.4"
}
],
"generated": "2025-04-01T21:50:03.833642Z",
"generated": "2025-04-08T08:36:36.076309Z",
"generator": "pub",
"generatorVersion": "3.5.0",
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",

View File

@ -73,54 +73,43 @@ class TransactionRepositoryImpl implements TransactionRepository {
if (seatData.exists) {
Map<String, dynamic>? data = seatData.data() as Map<String, dynamic>?;
// Fungsi untuk mendapatkan kelas seat dari nomor kursi (misalnya "A2" -> "a")
String getSeatClass(String seatNumber) {
// Ambil huruf pertama dan ubah ke lowercase
if (seatNumber.isNotEmpty) {
return seatNumber[0].toLowerCase();
}
return "a"; // Default ke "a" jika format tidak sesuai
return "a";
}
// Fungsi untuk mendapatkan indeks seat dari nomor kursi (misalnya "A2" -> 2)
int getSeatIndex(String seatNumber) {
if (seatNumber.length > 1) {
try {
return int.parse(seatNumber.substring(1)) - 1; // Konversi ke berbasis-0
return int.parse(seatNumber.substring(1)) - 1;
} catch (e) {
log("Error parsing seat index: $e");
}
}
return 0; // Default ke 0 jika format tidak sesuai
return 0;
}
// Perbarui status kursi untuk setiap seat yang dipilih
for (String seatNumber in numberSeat) {
String seatClass = getSeatClass(seatNumber);
int seatIndex = getSeatIndex(seatNumber);
log("Processing seat: $seatNumber, class: $seatClass, index: $seatIndex");
// Pastikan struktur data seat tersedia
if (data != null && data.containsKey('seat')) {
Map<String, dynamic> seatData = Map<String, dynamic>.from(data['seat']);
// Pastikan kelas kursi (a-f) tersedia
if (seatData.containsKey(seatClass)) {
Map<String, dynamic> classSeatData = Map<String, dynamic>.from(seatData[seatClass]);
// Pastikan array isTaken tersedia
if (classSeatData.containsKey('isTaken')) {
List<dynamic> isTaken = List<dynamic>.from(classSeatData['isTaken'] ?? []);
// Perbarui array isTaken
while (isTaken.length <= seatIndex) {
isTaken.add(false); // Tambahkan seat yang belum ada dengan false
isTaken.add(false);
}
isTaken[seatIndex] = true; // Set kursi yang dipilih sebagai 'taken'
isTaken[seatIndex] = true;
// Update Firestore untuk kelas kursi tertentu
await _firestore
.collection('tickets')
.doc(ticketId)
@ -130,7 +119,6 @@ class TransactionRepositoryImpl implements TransactionRepository {
log("Successfully updated seat $seatNumber in class $seatClass at index $seatIndex");
} else {
// Jika 'isTaken' tidak ada, buat array baru
List<dynamic> isTaken = List.filled(seatIndex + 1, false);
isTaken[seatIndex] = true;
@ -144,7 +132,6 @@ class TransactionRepositoryImpl implements TransactionRepository {
log("Created new isTaken array for seat $seatNumber in class $seatClass");
}
} else {
// Jika kelas kursi tidak ada, buat struktur baru
List<dynamic> isTaken = List.filled(seatIndex + 1, false);
isTaken[seatIndex] = true;
@ -155,7 +142,6 @@ class TransactionRepositoryImpl implements TransactionRepository {
log("Created new seat class $seatClass for seat $seatNumber");
}
} else {
// Jika struktur 'seat' tidak ada, buat struktur lengkap
List<dynamic> isTaken = List.filled(seatIndex + 1, false);
isTaken[seatIndex] = true;
@ -221,13 +207,6 @@ class TransactionRepositoryImpl implements TransactionRepository {
'updatedAt': now,
});
// Update status dokumen tiket utama
await _firestore.collection('tickets').doc(ticketId).update({
'status': status == 'paid' ? 'awaiting_verification' : status,
'lastUpdated': now,
});
// Update status di Realtime Database
final databaseRef = FirebaseDatabase.instance.ref();
await databaseRef.child('transactions/$userId/$ticketId/$transactionId/payment').update({
'status': status,
@ -239,36 +218,38 @@ class TransactionRepositoryImpl implements TransactionRepository {
}
@override
Future<void> uploadPaymentProof(
{required String ticketId, required String transactionId, required File proofImage}) async {
Future<void> uploadPaymentProof({
required String ticketId,
required String transactionId,
required File proofImage,
required String userId,
}) async {
try {
final fileName =
'payment_proof_${transactionId}_${DateTime.now().millisecondsSinceEpoch}${path.extension(proofImage.path)}';
final storageRef = _storage.ref().child('payment/$fileName');
// Upload file ke Firebase Storage
final uploadTask = await storageRef.putFile(proofImage);
final downloadUrl = await uploadTask.ref.getDownloadURL();
// Tambahkan listener untuk progress upload
final uploadTask = storageRef.putFile(proofImage);
// Update di Firestore dengan URL bukti pembayaran
// Upload file ke Firebase Storage
final snapshot = await uploadTask;
final downloadUrl = await snapshot.ref.getDownloadURL();
final now = DateTime.now();
// Update Firestore
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).update({
'proofUrl': downloadUrl,
'status': 'paid',
'paidAt': DateTime.now(),
'status': 'active',
'paidAt': now,
});
// Update status dokumen tiket utama
await _firestore.collection('tickets').doc(ticketId).update({
'status': 'awaiting_verification',
'lastUpdated': DateTime.now(),
});
// Update di Realtime Database
// Update Realtime Database
final databaseRef = FirebaseDatabase.instance.ref();
await databaseRef.child('transactions/$ticketId/payment').update({
await databaseRef.child('transactions/$userId/$ticketId/$transactionId/payment').update({
'proofUrl': downloadUrl,
'status': 'paid',
'paidAt': DateTime.now().millisecondsSinceEpoch,
'status': 'active',
'paidAt': now.millisecondsSinceEpoch,
});
} catch (e) {
throw Exception('Failed to upload payment proof: $e');
@ -278,12 +259,9 @@ class TransactionRepositoryImpl implements TransactionRepository {
@override
Future<List<TransactionModel>> getTransactionsByUserId(String userId) async {
try {
// Mencari tiket berdasarkan userId
final ticketQuerySnapshot = await _firestore.collection('tickets').where('userId', isEqualTo: userId).get();
final List<TransactionModel> transactions = [];
// Untuk setiap tiket, ambil data payment
for (var ticketDoc in ticketQuerySnapshot.docs) {
final ticketId = ticketDoc.id;
final paymentsSnapshot = await _firestore.collection('tickets').doc(ticketId).collection('payments').get();
@ -368,40 +346,194 @@ class TransactionRepositoryImpl implements TransactionRepository {
Future<void> checkAndCancelExpiredTransactions() async {
try {
log('[TransactionRepository] Mulai memeriksa transaksi kedaluwarsa');
final now = DateTime.now();
final pendingTransactionsSnapshot =
await _firestore.collectionGroup('payments').where('status', isEqualTo: 'pending').get();
for (var doc in pendingTransactionsSnapshot.docs) {
final data = doc.data();
final expiryTime = (data['expiryTime'] as Timestamp).toDate();
final ticketsCheck = await _firestore.collection('tickets').limit(1).get();
if (ticketsCheck.docs.isEmpty) {
log('[TransactionRepository] Tidak ada tiket untuk diperiksa, melewati pengecekan');
return;
}
if (expiryTime.isBefore(now)) {
final ticketId = data['ticketId'];
final transactionId = doc.id;
final userId = data['userDetails']['uid'];
try {
final pendingTransactionsSnapshot = await _firestore
.collectionGroup('payments')
.where('status', isEqualTo: 'pending')
.orderBy('createdAt', descending: false)
.get();
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).update({
'status': 'cancelled',
'updatedAt': now,
});
for (var doc in pendingTransactionsSnapshot.docs) {
final data = doc.data();
final expiryTime = (data['expiryTime'] as Timestamp).toDate();
await _firestore.collection('tickets').doc(ticketId).update({
'status': 'cancelled',
'lastUpdated': now,
});
// Jika sudah melewati waktu pembayaran
if (expiryTime.isBefore(now)) {
final ticketId = data['ticketId'];
final transactionId = doc.id;
final userId = data['userDetails']['uid'];
final flightId = data['flightId'];
final numberSeat = (data['numberSeat'] as List).map((e) => e.toString()).toList();
final databaseRef = FirebaseDatabase.instance.ref();
await databaseRef.child('transactions/$userId/$ticketId/$transactionId/payment').update({
'status': 'cancelled',
'updatedAt': now.millisecondsSinceEpoch,
});
// Update status di Firestore
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).update({
'status': 'cancelled',
'updatedAt': now,
});
log('Transaction $transactionId cancelled due to expiry');
// Reset status kursi
await _resetSeatStatus(ticketId, flightId, numberSeat);
// Update status di Realtime Database
final databaseRef = FirebaseDatabase.instance.ref();
await databaseRef.child('transactions/$userId/$ticketId/$transactionId/payment').update({
'status': 'cancelled',
'updatedAt': now.millisecondsSinceEpoch,
});
log('Transaksi $transactionId dibatalkan karena kedaluwarsa');
}
}
} catch (e) {
if (e.toString().contains('failed-precondition') || e.toString().contains('requires an index')) {
log('[TransactionRepository] Indeks belum tersedia. Menggunakan metode alternatif.');
// Gunakan metode alternatif yang tidak memerlukan indeks khusus
await checkAndCancelExpiredTransactionsAlternative();
} else {
rethrow;
}
}
} catch (e) {
log('Error checking expired transactions: $e');
log('[TransactionRepository] Error memeriksa transaksi kedaluwarsa: $e');
rethrow;
}
}
// Metode helper untuk mereset status kursi
Future<void> _resetSeatStatus(String ticketId, String flightId, List<String> numberSeat) async {
try {
DocumentSnapshot seatData =
await _firestore.collection('tickets').doc(ticketId).collection('flights').doc(flightId).get();
if (seatData.exists) {
Map<String, dynamic>? data = seatData.data() as Map<String, dynamic>?;
for (String seatNumber in numberSeat) {
String seatClass = getSeatClass(seatNumber);
int seatIndex = getSeatIndex(seatNumber);
if (data != null && data.containsKey('seat')) {
Map<String, dynamic> seatData = Map<String, dynamic>.from(data['seat']);
if (seatData.containsKey(seatClass)) {
Map<String, dynamic> classSeatData = Map<String, dynamic>.from(seatData[seatClass]);
if (classSeatData.containsKey('isTaken')) {
List<dynamic> isTaken = List<dynamic>.from(classSeatData['isTaken'] ?? []);
if (seatIndex < isTaken.length) {
isTaken[seatIndex] = false; // Reset status kursi
await _firestore
.collection('tickets')
.doc(ticketId)
.collection('flights')
.doc(flightId)
.update({'seat.$seatClass.isTaken': isTaken});
log("Reset kursi $seatNumber di kelas $seatClass pada indeks $seatIndex");
}
}
}
}
}
}
} catch (e) {
log("Error mereset status kursi: $e");
}
}
String getSeatClass(String seatNumber) {
if (seatNumber.isNotEmpty) {
return seatNumber[0].toLowerCase();
}
return "a";
}
int getSeatIndex(String seatNumber) {
if (seatNumber.length > 1) {
try {
return int.parse(seatNumber.substring(1)) - 1;
} catch (e) {
log("Error parsing seat index: $e");
}
}
return 0;
}
Future<void> checkAndCancelExpiredTransactionsAlternative() async {
try {
final now = DateTime.now();
log('[TransactionRepository] Menggunakan metode alternatif untuk memeriksa transaksi');
final ticketsSnapshot = await _firestore.collection('tickets').get();
int count = 0;
for (var ticketDoc in ticketsSnapshot.docs) {
final ticketId = ticketDoc.id;
final paymentsSnapshot = await _firestore
.collection('tickets')
.doc(ticketId)
.collection('payments')
.where('status', isEqualTo: 'pending')
.get();
for (var doc in paymentsSnapshot.docs) {
final data = doc.data();
if (data.containsKey('expiryTime')) {
final expiryTime = (data['expiryTime'] as Timestamp).toDate();
if (expiryTime.isBefore(now)) {
final transactionId = doc.id;
final userId = data['userDetails']?['uid'];
final flightId = data['flightId'];
final List<String> numberSeat = [];
if (data.containsKey('numberSeat')) {
numberSeat.addAll((data['numberSeat'] as List).map((e) => e.toString()));
}
log('[TransactionRepository] Memproses transaksi kedaluwarsa alternatif: $transactionId');
// Update status transaksi
await _firestore.collection('tickets').doc(ticketId).collection('payments').doc(transactionId).update({
'status': 'cancelled',
'updatedAt': now,
});
// Reset seat status
if (numberSeat.isNotEmpty) {
await _resetSeatStatus(ticketId, flightId, numberSeat);
}
// Update Realtime Database jika userId tersedia
if (userId != null) {
final databaseRef = FirebaseDatabase.instance.ref();
await databaseRef.child('transactions/$userId/$ticketId/$transactionId/payment').update({
'status': 'cancelled',
'updatedAt': now.millisecondsSinceEpoch,
});
}
count++;
}
}
}
}
log('[TransactionRepository] Selesai memeriksa transaksi (metode alternatif), $count transaksi dibatalkan');
} catch (e) {
log('[TransactionRepository] Error pada metode alternatif: $e');
}
}
}

View File

@ -0,0 +1,15 @@
import 'package:get/get.dart';
import 'package:e_porter/data/repositories/transaction_repository_impl.dart';
import 'package:e_porter/_core/service/transaction_expiry_service.dart';
class AppBinding extends Bindings {
@override
void dependencies() {
// Inisialisasi TransactionExpiryService
Get.put<TransactionRepositoryImpl>(TransactionRepositoryImpl(), permanent: true);
// Inisialisasi dan mulai service
final repository = Get.find<TransactionRepositoryImpl>();
TransactionExpiryService().initialize(repository);
}
}

View File

@ -4,22 +4,29 @@ import 'package:e_porter/data/repositories/transaction_repository_impl.dart';
import 'package:e_porter/domain/usecases/transaction_usecase.dart';
import 'package:e_porter/presentation/controllers/transaction_controller.dart';
import '../../_core/service/transaction_expiry_service.dart';
class TransactionBinding extends Bindings {
@override
void dependencies() {
// Repository
Get.lazyPut<TransactionRepository>(
() => TransactionRepositoryImpl(),
fenix: true
);
Get.lazyPut<TransactionRepositoryImpl>(
() => TransactionRepositoryImpl(),
fenix: true,
);
// UseCase
Get.lazyPut<TransactionUseCase>(
() => TransactionUseCase(Get.find<TransactionRepository>()),
);
// Controller
Get.lazyPut<TransactionController>(
() => TransactionController(Get.find<TransactionUseCase>()),
);
TransactionExpiryService().initialize(Get.find<TransactionRepositoryImpl>());
}
}

View File

@ -28,6 +28,7 @@ abstract class TransactionRepository {
required String ticketId,
required String transactionId,
required File proofImage,
required String userId,
});
Future<List<TransactionModel>> getTransactionsByUserId(String userId);

View File

@ -55,11 +55,13 @@ class TransactionUseCase {
required String ticketId,
required String transactionId,
required File proofImage,
required String userId,
}) {
return _repository.uploadPaymentProof(
ticketId: ticketId,
transactionId: transactionId,
proofImage: proofImage,
userId: userId,
);
}

View File

@ -1,19 +1,14 @@
import 'dart:developer';
import 'package:e_porter/data/repositories/transaction_repository_impl.dart';
import 'package:e_porter/domain/bindings/app_binding.dart';
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '_core/service/transaction_expiry_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final transactionRepo = Get.put(TransactionRepositoryImpl());
TransactionExpiryService().initialize(transactionRepo);
log("Firebase Initialized Successfully!");
runApp(MyApp(initialRoute: Routes.SPLASH));
@ -32,6 +27,7 @@ class MyApp extends StatelessWidget {
return GetMaterialApp(
debugShowCheckedModeBanner: true,
initialRoute: initialRoute,
initialBinding: AppBinding(),
getPages: AppRoutes.routes,
);
},

View File

@ -1,8 +1,10 @@
import 'dart:developer';
import 'dart:io';
import 'package:get/get.dart';
import 'package:e_porter/domain/models/transaction_model.dart';
import 'package:e_porter/domain/usecases/transaction_usecase.dart';
import 'package:e_porter/_core/service/logger_service.dart';
import '../../data/repositories/transaction_repository_impl.dart';
class TransactionController extends GetxController {
final TransactionUseCase _transactionUseCase;
@ -12,6 +14,8 @@ class TransactionController extends GetxController {
final Rx<TransactionModel?> currentTransaction = Rx<TransactionModel?>(null);
final RxBool isLoading = false.obs;
final RxString error = ''.obs;
final RxBool isUploading = false.obs;
final RxDouble uploadProgress = 0.0.obs;
Future<String> createTransaction({
required String ticketId,
@ -46,7 +50,6 @@ class TransactionController extends GetxController {
numberSeat: numberSeat,
);
// Ambil data transaksi setelah berhasil dibuat
final transaction = await _transactionUseCase.getTransactionById(
ticketId: ticketId,
transactionId: transactionId,
@ -55,7 +58,7 @@ class TransactionController extends GetxController {
currentTransaction.value = transaction;
return transactionId;
} catch (e) {
logger.e('Gagal membuat transaksi: $e');
log('Gagal membuat transaksi: $e');
error.value = 'Gagal membuat transaksi: $e';
throw Exception('Gagal membuat transaksi: $e');
} finally {
@ -63,26 +66,45 @@ class TransactionController extends GetxController {
}
}
Future<void> uploadPaymentProof(
{required String ticketId, required String transactionId, required File proofImage}) async {
Future<void> uploadPaymentProof({
required String ticketId,
required String transactionId,
required File proofImage,
required String userId,
}) async {
try {
isLoading.value = true;
isUploading.value = true;
error.value = '';
uploadProgress.value = 0.0;
await _transactionUseCase.uploadPaymentProof(
ticketId: ticketId, transactionId: transactionId, proofImage: proofImage);
// Refresh data transaksi setelah bukti pembayaran diunggah
final transaction =
await _transactionUseCase.getTransactionById(ticketId: ticketId, transactionId: transactionId);
ticketId: ticketId,
transactionId: transactionId,
proofImage: proofImage,
userId: userId,
);
// Update status transaksi setelah upload berhasil
await _transactionUseCase.updateTransactionStatus(
ticketId: ticketId,
transactionId: transactionId,
status: 'active',
userId: userId,
);
// Refresh data transaksi
final transaction = await _transactionUseCase.getTransactionById(
ticketId: ticketId,
transactionId: transactionId,
);
currentTransaction.value = transaction;
} catch (e) {
logger.e('Gagal mengunggah bukti pembayaran: $e');
error.value = 'Gagal mengunggah bukti pembayaran: $e';
throw Exception('Gagal mengunggah bukti pembayaran: $e');
error.value = 'Gagal mengupload bukti pembayaran: $e';
throw Exception('Gagal mengupload bukti pembayaran: $e');
} finally {
isLoading.value = false;
isUploading.value = false;
}
}
@ -94,7 +116,7 @@ class TransactionController extends GetxController {
final transactions = await _transactionUseCase.getTransactionsByUserId(userId);
return transactions;
} catch (e) {
logger.e('Gagal mendapatkan daftar transaksi: $e');
log('Gagal mendapatkan daftar transaksi: $e');
error.value = 'Gagal mendapatkan daftar transaksi: $e';
throw Exception('Gagal mendapatkan daftar transaksi: $e');
} finally {
@ -112,7 +134,7 @@ class TransactionController extends GetxController {
currentTransaction.value = transaction;
} catch (e) {
logger.e('Gagal mendapatkan detail transaksi: $e');
log('Gagal mendapatkan detail transaksi: $e');
error.value = 'Gagal mendapatkan detail transaksi: $e';
throw Exception('Gagal mendapatkan detail transaksi: $e');
} finally {
@ -127,12 +149,12 @@ class TransactionController extends GetxController {
currentTransaction.value = transaction;
},
onError: (e) {
logger.e('Error mendengarkan perubahan transaksi: $e');
log('Error mendengarkan perubahan transaksi: $e');
error.value = 'Error mendengarkan perubahan transaksi: $e';
},
);
} catch (e) {
logger.e('Gagal memantau transaksi: $e');
log('Gagal memantau transaksi: $e');
error.value = 'Gagal memantau transaksi: $e';
}
}
@ -162,11 +184,28 @@ class TransactionController extends GetxController {
currentTransaction.value = transaction;
} catch (e) {
logger.e('Gagal mengupdate status transaksi: $e');
log('Gagal mengupdate status transaksi: $e');
error.value = 'Gagal mengupdate status transaksi: $e';
throw Exception('Gagal mengupdate status transaksi: $e');
} finally {
isLoading.value = false;
}
}
Future<void> checkExpiredTransactions() async {
try {
isLoading.value = true;
error.value = '';
final repository = Get.find<TransactionRepositoryImpl>();
await repository.checkAndCancelExpiredTransactions();
log('Berhasil memeriksa transaksi kedaluwarsa');
} catch (e) {
log('Gagal memeriksa transaksi kedaluwarsa: $e');
error.value = 'Gagal memeriksa transaksi kedaluwarsa: $e';
} finally {
isLoading.value = false;
}
}
}

View File

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:e_porter/_core/component/appbar/appbar_component.dart';
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
import 'package:e_porter/_core/constants/colors.dart';
@ -125,7 +127,14 @@ class _PaymentScreenState extends State<PaymentScreen> {
text: 'Lanjutkan',
textColor: Colors.white,
onTap: () {
Get.toNamed(Routes.UPLOADFILE);
final argument = {
'ticketId': ticketId,
'transactionId': Get.arguments['transactionId'], // Ambil dari argumen yang diteruskan
'flightData': flightData,
'totalAll': totalAll,
};
log('Transaction ID: $argument');
Get.toNamed(Routes.UPLOADFILE, arguments: argument);
},
),
),

View File

@ -65,6 +65,7 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
selectedPorterServices = args['selectedPorterServices'] ?? {};
fetchDataFlight();
transactionController.checkExpiredTransactions();
}
Future<void> fetchDataFlight() async {
@ -238,7 +239,7 @@ class _TicketBookingStep4ScreenState extends State<TicketBookingStep4Screen> {
// Persiapkan data expiry time
final DateTime currentTime = DateTime.now();
final DateTime expiryTime = currentTime.add(Duration(seconds: 5));
final DateTime expiryTime = currentTime.add(Duration(days: 1));
// Persiapkan data bandara
final bandaraData = {

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/constants/typography.dart';
import 'package:e_porter/_core/service/permission_service.dart';
@ -11,7 +13,10 @@ import 'package:zoom_tap_animation/zoom_tap_animation.dart';
import '../../../../_core/component/appbar/appbar_component.dart';
import '../../../../_core/component/button/button_fill.dart';
import '../../../../_core/component/card/custome_shadow_cotainner.dart';
import '../../../../_core/service/preferences_service.dart';
import '../../../../domain/models/upload_file_model.dart';
import '../../../controllers/transaction_controller.dart';
import '../../routes/app_rountes.dart';
class UploadFileScreen extends StatefulWidget {
const UploadFileScreen({super.key});
@ -22,8 +27,100 @@ class UploadFileScreen extends StatefulWidget {
class _UploadFileScreenState extends State<UploadFileScreen> {
final List<UploadFileModel> uploadedFiles = [];
final TransactionController _transactionController = Get.find<TransactionController>();
bool isUploading = false;
late String ticketId;
late String transactionId;
late String userId = '';
@override
void initState() {
super.initState();
final args = Get.arguments as Map<String, dynamic>;
ticketId = args['ticketId'] ?? '';
transactionId = args['transactionId'] ?? '';
_loadUserData();
}
Future<void> _loadUserData() async {
final userData = await PreferencesService.getUserData();
if (userData != null) {
setState(() {
userId = userData.uid;
});
}
}
void _uploadToServer() async {
try {
// Validasi data transaksi
if (ticketId.isEmpty || transactionId.isEmpty || userId.isEmpty) {
Get.snackbar(
'Error',
'Data transaksi tidak lengkap',
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
if (uploadedFiles.isEmpty || uploadedFiles.first.status != FileUploadStatus.completed) {
Get.snackbar(
'Error',
'Silakan pilih dan selesaikan proses file terlebih dahulu',
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
setState(() {
isUploading = true;
});
Get.snackbar(
'Info',
'Mengupload bukti pembayaran...',
backgroundColor: PrimaryColors.primary600,
colorText: Colors.white,
);
final fileToUpload = uploadedFiles.first;
final File proofImage = File(fileToUpload.filePath);
await _transactionController.uploadPaymentProof(
ticketId: ticketId,
transactionId: transactionId,
proofImage: proofImage,
userId: userId,
);
Get.snackbar(
'Sukses',
'Bukti pembayaran berhasil diupload',
backgroundColor: Colors.green,
colorText: Colors.white,
);
Get.offAllNamed(Routes.NAVBAR);
} catch (e) {
Get.snackbar(
'Error',
'Gagal mengupload bukti pembayaran: $e',
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
if (mounted) {
setState(() {
isUploading = false;
});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -225,52 +322,51 @@ class _UploadFileScreenState extends State<UploadFileScreen> {
}
Future<void> _pickFile() async {
bool permissionGranted = await PermissionHelper.requestStoragePermission();
if (!permissionGranted) {
Get.snackbar(
'Permission Denied',
'Storage permission is required to select files.',
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
bool permissionGranted = await PermissionHelper.requestStoragePermission();
if (!permissionGranted) {
Get.snackbar(
'Permission Denied',
'Storage permission is required to select files.',
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
try {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf', 'zip', 'mp4'],
);
if (result != null) {
final file = result.files.first;
final newFile = UploadFileModel(
fileName: file.name,
filePath: file.path!,
fileSize: file.size,
progress: 0,
remainingTime: '0',
status: FileUploadStatus.pending,
try {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf'],
);
setState(() {
uploadedFiles.add(newFile);
});
if (result != null) {
final file = result.files.first;
final newFile = UploadFileModel(
fileName: file.name,
filePath: file.path!,
fileSize: file.size,
progress: 0,
remainingTime: '0',
status: FileUploadStatus.pending,
);
// Simulasikan proses loading lokal
_simulateLocalLoading(newFile);
setState(() {
uploadedFiles.add(newFile);
});
// Simulasikan proses loading lokal
_simulateLocalLoading(newFile);
}
} catch (e) {
print('Error picking file: $e');
Get.snackbar(
'Error',
'Terjadi kesalahan saat memilih file',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
} catch (e) {
print('Error picking file: $e');
Get.snackbar(
'Error',
'Terjadi kesalahan saat memilih file',
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
void _simulateLocalLoading(UploadFileModel file) {
setState(() {
@ -349,31 +445,4 @@ class _UploadFileScreenState extends State<UploadFileScreen> {
_uploadToServer();
}
}
void _uploadToServer() {
// Simulate uploading to server
Get.snackbar(
'Info',
'Mengupload bukti pembayaran...',
backgroundColor: PrimaryColors.primary600,
colorText: Colors.white,
);
// Here you would actually send the files to your server
// For now, let's just simulate a successful upload
Future.delayed(Duration(seconds: 2), () {
Get.snackbar(
'Sukses',
'Bukti pembayaran berhasil diupload',
backgroundColor: Colors.green,
colorText: Colors.white,
);
// Navigate back or to a success screen
Future.delayed(Duration(seconds: 1), () {
// Get.offNamed(Routes.SUCCESS_SCREEN);
Get.back();
});
});
}
}

View File

@ -59,6 +59,7 @@ dependencies:
path: ^1.9.0
permission_handler: ^11.4.0
device_info_plus: ^11.3.0
# workmanager: ^0.5.2
# pin_code_fields: ^8.0.1
# dio: ^5.8.0+1