Feat: done choose seat and choose service porter
This commit is contained in:
parent
31e49efa3a
commit
210209b376
|
@ -500,7 +500,7 @@
|
|||
"languageVersion": "3.4"
|
||||
}
|
||||
],
|
||||
"generated": "2025-03-18T17:25:46.276961Z",
|
||||
"generated": "2025-03-21T19:39:10.352546Z",
|
||||
"generator": "pub",
|
||||
"generatorVersion": "3.5.0",
|
||||
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.9999 2C10.3024 2 8.93591 3.33547 8.80072 5H3.24994C3.15056 4.99859 3.05188 5.01696 2.95966 5.05402C2.86743 5.09108 2.78349 5.1461 2.71271 5.21588C2.64194 5.28566 2.58573 5.36882 2.54737 5.46051C2.50901 5.5522 2.48926 5.65061 2.48926 5.75C2.48926 5.84939 2.50901 5.9478 2.54737 6.03949C2.58573 6.13118 2.64194 6.21434 2.71271 6.28412C2.78349 6.3539 2.86743 6.40892 2.95966 6.44598C3.05188 6.48304 3.15056 6.50141 3.24994 6.5H4.08978L5.78119 19.6016C5.95769 20.9686 7.13073 22 8.50873 22H11.8163C11.3888 21.5555 11.0252 21.0515 10.7372 20.5H8.50775C7.88125 20.5 7.34899 20.0312 7.26849 19.4102L5.60248 6.5H18.3964L17.7997 11.1309C18.3082 11.2339 18.7915 11.4017 19.249 11.6162L19.9091 6.5H20.7499C20.8493 6.50141 20.948 6.48304 21.0402 6.44598C21.1324 6.40892 21.2164 6.3539 21.2872 6.28412C21.3579 6.21434 21.4141 6.13118 21.4525 6.03949C21.4909 5.9478 21.5106 5.84939 21.5106 5.75C21.5106 5.65061 21.4909 5.5522 21.4525 5.46051C21.4141 5.36882 21.3579 5.28566 21.2872 5.21588C21.2164 5.1461 21.1324 5.09108 21.0402 5.05402C20.948 5.01696 20.8493 4.99859 20.7499 5H15.1992C15.064 3.33547 13.6974 2 11.9999 2ZM11.9999 3.5C12.886 3.5 13.5759 4.15153 13.7001 5H10.2997C10.424 4.15153 11.1139 3.5 11.9999 3.5ZM16.4999 12C13.4624 12 10.9999 14.4625 10.9999 17.5C10.9999 20.5375 13.4624 23 16.4999 23C19.5374 23 21.9999 20.5375 21.9999 17.5C21.9999 14.4625 19.5374 12 16.4999 12ZM18.9999 14.5C19.0566 14.5 19.1129 14.513 19.1669 14.5322C19.1861 14.5391 19.2042 14.5493 19.2226 14.5586C19.269 14.582 19.3147 14.6077 19.3535 14.6465C19.3946 14.6876 19.4224 14.7366 19.4462 14.7861C19.4531 14.8005 19.4613 14.8143 19.4667 14.8291C19.5072 14.9397 19.508 15.0603 19.4677 15.1709C19.4618 15.1872 19.452 15.202 19.4443 15.2178C19.4206 15.266 19.3937 15.3134 19.3535 15.3535L17.207 17.5C18.3045 18.597 19.3095 19.6025 19.3535 19.6465C19.3928 19.6859 19.4188 19.7331 19.4423 19.7803C19.4509 19.7976 19.4613 19.8141 19.4677 19.832C19.4873 19.8866 19.4999 19.9427 19.4999 20C19.4999 20.0567 19.4869 20.113 19.4677 20.167C19.4609 20.1862 19.4506 20.2042 19.4413 20.2227C19.4179 20.269 19.3923 20.3148 19.3535 20.3535C19.3123 20.3946 19.2634 20.4225 19.2138 20.4463C19.1995 20.4532 19.1857 20.4614 19.1708 20.4668C19.0602 20.5073 18.9396 20.508 18.829 20.4678C18.8127 20.4618 18.7979 20.452 18.7822 20.4443C18.7339 20.4207 18.6865 20.3937 18.6464 20.3535L16.4999 18.207L14.3535 20.3535C14.3141 20.3929 14.2669 20.4189 14.2197 20.4424C14.2024 20.4509 14.1859 20.4613 14.1679 20.4678C14.1133 20.4874 14.0572 20.5 13.9999 20.5C13.9432 20.5 13.887 20.487 13.8329 20.4678C13.8137 20.4609 13.7957 20.4507 13.7773 20.4414C13.7309 20.418 13.6851 20.3923 13.6464 20.3535C13.6053 20.3124 13.5775 20.2634 13.5537 20.2139C13.5468 20.1995 13.5386 20.1857 13.5331 20.1709C13.4926 20.0603 13.4919 19.9397 13.5322 19.8291C13.5381 19.8128 13.5479 19.798 13.5556 19.7822C13.5793 19.734 13.6062 19.6866 13.6464 19.6465C13.6904 19.6025 14.6954 18.597 15.7929 17.5L13.6464 15.3535C13.607 15.3141 13.5811 15.2669 13.5576 15.2197C13.549 15.2024 13.5386 15.1859 13.5322 15.168C13.5126 15.1134 13.4999 15.0573 13.4999 15C13.4999 14.9433 13.513 14.887 13.5322 14.833C13.539 14.8138 13.5493 14.7958 13.5585 14.7773C13.5819 14.731 13.6076 14.6852 13.6464 14.6465C13.6932 14.5997 13.7482 14.5654 13.8056 14.541C13.8145 14.5372 13.8239 14.5355 13.8329 14.5322C13.8806 14.5152 13.9295 14.506 13.9794 14.5039C13.9905 14.5034 14.0016 14.5017 14.0126 14.502C14.0693 14.5034 14.126 14.5135 14.1796 14.5342C14.1888 14.5377 14.197 14.5437 14.206 14.5479C14.259 14.572 14.3099 14.6028 14.3535 14.6465C14.3975 14.6905 15.4029 15.6955 16.4999 16.793L18.6464 14.6465C18.6858 14.6071 18.733 14.5811 18.7802 14.5576C18.7975 14.5491 18.814 14.5387 18.832 14.5322C18.8866 14.5126 18.9426 14.5 18.9999 14.5Z" fill="#004B87"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
|
@ -47,11 +47,12 @@ class CustomeIcons {
|
|||
static SvgPicture PlusOutline({double? size, Color? color}) => getIcon('ic_plus', color: color);
|
||||
static SvgPicture RefreshOutline({double? size, Color? color}) => getIcon('ic_refresh', color: color);
|
||||
static SvgPicture RunningOutline({double? size, Color? color}) => getIcon('ic_running', color: color);
|
||||
static SvgPicture VIPFilled({double? size, Color? color}) => getIcon('ic_vip', color: color);
|
||||
static SvgPicture LockFilled({double? size, Color? color}) => getIcon('ic_lock', color: color);
|
||||
static SvgPicture AddUserFemaleFilled({double? size, Color? color}) => getIcon('ic_add_user_female', color: color);
|
||||
static SvgPicture LogoutFilled({double? size, Color? color}) => getIcon('ic_logout', color: color);
|
||||
static SvgPicture LockOutline({double? size, Color? color}) => getIcon('ic_lock', color: color);
|
||||
static SvgPicture AddUserFemaleOutline({double? size, Color? color}) => getIcon('ic_add_user_female', color: color);
|
||||
static SvgPicture LogoutOutline({double? size, Color? color}) => getIcon('ic_logout', color: color);
|
||||
static SvgPicture RemoveOutline({double? size, Color? color}) => getIcon('ic_remove', color: color);
|
||||
|
||||
static SvgPicture FlightSeatFilled({double? size, Color? color}) => getIcon('ic_flight_seat_filled', color: color);
|
||||
static SvgPicture PlaneRightFilled({double? size, Color? color}) => getIcon('ic_plane_filled', color: color);
|
||||
static SvgPicture VIPFilled({double? size, Color? color}) => getIcon('ic_vip', color: color);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// data/repositories/porter_service_repository_impl.dart
|
||||
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import '../../domain/models/porter_service_model.dart';
|
||||
import '../../domain/repositories/porter_service_repository.dart';
|
||||
|
||||
class PorterServiceRepositoryImpl implements PorterServiceRepository {
|
||||
final FirebaseFirestore _firestore;
|
||||
final String _collectionName = 'PorterServices';
|
||||
|
||||
PorterServiceRepositoryImpl({FirebaseFirestore? firestore})
|
||||
: _firestore = firestore ?? FirebaseFirestore.instance;
|
||||
|
||||
@override
|
||||
Future<List<PorterServiceModel>> getAllServices() async {
|
||||
try {
|
||||
final snapshot = await _firestore.collection(_collectionName).get();
|
||||
final services = snapshot.docs.map((doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return PorterServiceModel.fromJson(data, doc.id);
|
||||
}).toList();
|
||||
return services;
|
||||
} catch (e) {
|
||||
throw Exception('Error mengambil daftar layanan porter: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<PorterServiceModel>> getServicesByType(String type) async {
|
||||
try {
|
||||
final snapshot = await _firestore
|
||||
.collection(_collectionName)
|
||||
.where('availableFor', arrayContains: type)
|
||||
.orderBy('sort')
|
||||
.get();
|
||||
|
||||
return snapshot.docs.map((doc) {
|
||||
final data = doc.data() as Map<String, dynamic>;
|
||||
return PorterServiceModel.fromJson(data, doc.id);
|
||||
}).toList();
|
||||
} catch (e) {
|
||||
throw Exception('Error mendapatkan layanan berdasarkan tipe: $e');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// domain/bindings/porter_service_binding.dart
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
import '../../domain/repositories/porter_service_repository.dart';
|
||||
import '../../data/repositories/porter_service_repository_impl.dart';
|
||||
import '../../domain/usecases/porter_service_usecase.dart';
|
||||
import '../../presentation/controllers/porter_service_controller.dart';
|
||||
|
||||
class PorterServiceBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
// Inisialisasi Firestore instance
|
||||
final firestore = FirebaseFirestore.instance;
|
||||
|
||||
// Repository - Injeksi implementasi ke abstraksi
|
||||
Get.lazyPut<PorterServiceRepository>(
|
||||
() => PorterServiceRepositoryImpl(firestore: firestore),
|
||||
);
|
||||
|
||||
// UseCase
|
||||
Get.lazyPut<PorterServiceUseCase>(
|
||||
() => PorterServiceUseCase(
|
||||
Get.find<PorterServiceRepository>(),
|
||||
),
|
||||
);
|
||||
|
||||
// Controller
|
||||
Get.lazyPut<PorterServiceController>(
|
||||
() => PorterServiceController(
|
||||
Get.find<PorterServiceUseCase>(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
class PorterServiceModel {
|
||||
final String? id;
|
||||
final String name;
|
||||
final int price;
|
||||
final String description;
|
||||
final List<String> tersediaUntuk;
|
||||
final int sort;
|
||||
|
||||
PorterServiceModel({
|
||||
this.id,
|
||||
required this.name,
|
||||
required this.price,
|
||||
required this.description,
|
||||
required this.tersediaUntuk,
|
||||
required this.sort,
|
||||
});
|
||||
|
||||
factory PorterServiceModel.fromJson(Map<String, dynamic> json, [String? docId]) {
|
||||
List<String> parseTersediaUntuk(dynamic tersediaUntuk) {
|
||||
if (tersediaUntuk is List) {
|
||||
return tersediaUntuk.map((item) => item.toString()).toList();
|
||||
} else if (tersediaUntuk is Map) {
|
||||
return tersediaUntuk.entries.map((entry) => entry.value.toString()).toList();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return PorterServiceModel(
|
||||
id: docId ?? json['id'],
|
||||
name: json['name'] ?? '',
|
||||
price: json['price'] ?? 0,
|
||||
description: json['description'] ?? '',
|
||||
tersediaUntuk: parseTersediaUntuk(json['availableFor']),
|
||||
sort: json['sort'] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'price': price,
|
||||
'description': description,
|
||||
'availableFor': tersediaUntuk,
|
||||
'sort': sort,
|
||||
};
|
||||
}
|
||||
|
||||
PorterServiceModel copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
int? price,
|
||||
String? description,
|
||||
List<String>? tersediaUntuk,
|
||||
int? sort,
|
||||
}) {
|
||||
return PorterServiceModel(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
price: price ?? this.price,
|
||||
description: description ?? this.description,
|
||||
tersediaUntuk: tersediaUntuk ?? this.tersediaUntuk,
|
||||
sort: sort ?? this.sort,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ class FlightModel {
|
|||
final String cityDeparture;
|
||||
final String cityArrival;
|
||||
final String codeDeparture;
|
||||
final String codeTransit;
|
||||
final String codeArrival;
|
||||
final DateTime departureTime;
|
||||
final DateTime arrivalTime;
|
||||
|
@ -58,6 +59,7 @@ class FlightModel {
|
|||
required this.cityDeparture,
|
||||
required this.cityArrival,
|
||||
required this.codeDeparture,
|
||||
required this.codeTransit,
|
||||
required this.codeArrival,
|
||||
required this.departureTime,
|
||||
required this.arrivalTime,
|
||||
|
@ -91,6 +93,7 @@ class FlightModel {
|
|||
cityDeparture: data['cityDeparture'] ?? '',
|
||||
cityArrival: data['cityArrival'] ?? '',
|
||||
codeDeparture: data['codeDeparture'] ?? '',
|
||||
codeTransit: data['codeTransit'] ?? '',
|
||||
codeArrival: data['codeArrival'] ?? '',
|
||||
departureTime: (data['dateDeparture'] as Timestamp).toDate(),
|
||||
arrivalTime: (data['dateArrival'] as Timestamp).toDate(),
|
||||
|
@ -109,6 +112,7 @@ class FlightModel {
|
|||
'cityDeparture': cityDeparture,
|
||||
'cityArrival': cityArrival,
|
||||
'codeDeparture': codeDeparture,
|
||||
'codeTransit': codeTransit,
|
||||
'codeArrival': codeArrival,
|
||||
'dateDeparture': Timestamp.fromDate(departureTime),
|
||||
'dateArrival': Timestamp.fromDate(arrivalTime),
|
||||
|
|
|
@ -53,7 +53,7 @@ class UserData {
|
|||
|
||||
return UserData(
|
||||
uid: map['uid'] ?? '',
|
||||
tipeId: map['tipeId'] ?? '',
|
||||
tipeId: map['typeId'] ?? '',
|
||||
noId: map['noId'] ?? '',
|
||||
name: map['name'] as String?,
|
||||
email: map['email'] as String?,
|
||||
|
@ -70,7 +70,7 @@ class UserData {
|
|||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'uid': uid,
|
||||
'tipeId': tipeId,
|
||||
'typeId': tipeId,
|
||||
'noId': noId,
|
||||
'name': name,
|
||||
'email': email,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import '../models/porter_service_model.dart';
|
||||
|
||||
abstract class PorterServiceRepository {
|
||||
// Mendapatkan semua layanan porter
|
||||
Future<List<PorterServiceModel>> getAllServices();
|
||||
|
||||
// Mendapatkan layanan porter berdasarkan tipe (departure, arrival, transit)
|
||||
Future<List<PorterServiceModel>> getServicesByType(String type);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// domain/usecases/porter_service_usecase.dart
|
||||
|
||||
import '../models/porter_service_model.dart';
|
||||
import '../repositories/porter_service_repository.dart';
|
||||
|
||||
class PorterServiceUseCase {
|
||||
final PorterServiceRepository _repository;
|
||||
|
||||
PorterServiceUseCase(this._repository);
|
||||
|
||||
Future<List<PorterServiceModel>> getAllServices() async {
|
||||
return await _repository.getAllServices();
|
||||
}
|
||||
|
||||
Future<List<PorterServiceModel>> getAllServicesOrderedByUrutan() async {
|
||||
final services = await _repository.getAllServices();
|
||||
services.sort((a, b) => a.sort.compareTo(b.sort));
|
||||
return services;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// presentation/controllers/porter_service_controller.dart
|
||||
|
||||
import 'package:e_porter/_core/service/logger_service.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../domain/models/porter_service_model.dart';
|
||||
import '../../domain/usecases/porter_service_usecase.dart';
|
||||
|
||||
class PorterServiceController extends GetxController {
|
||||
final PorterServiceUseCase _porterServiceUseCase;
|
||||
|
||||
PorterServiceController(this._porterServiceUseCase);
|
||||
|
||||
final RxList<PorterServiceModel> layananPorter = <PorterServiceModel>[].obs;
|
||||
final RxList<PorterServiceModel> layananPorterArrival = <PorterServiceModel>[].obs;
|
||||
final RxList<PorterServiceModel> layananPorterDeparture = <PorterServiceModel>[].obs;
|
||||
final RxList<PorterServiceModel> layananPorterTransit = <PorterServiceModel>[].obs;
|
||||
|
||||
final RxBool isLoading = false.obs;
|
||||
final RxBool hasError = false.obs;
|
||||
final RxString pesanError = ''.obs;
|
||||
final RxString tipeTerpilih = 'semua'.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
fetchLayananPorter();
|
||||
}
|
||||
|
||||
Future<void> fetchLayananPorter() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
hasError.value = false;
|
||||
pesanError.value = '';
|
||||
|
||||
final result = await _porterServiceUseCase.getAllServicesOrderedByUrutan();
|
||||
|
||||
layananPorterArrival.assignAll(result.where((service) => service.tersediaUntuk.contains('departure')).toList());
|
||||
layananPorterDeparture.assignAll(result.where((service) => service.tersediaUntuk.contains('arrival')).toList());
|
||||
layananPorterTransit.assignAll(result.where((service) => service.tersediaUntuk.contains('transit')).toList());
|
||||
} catch (e) {
|
||||
logger.e('Error mendapatkan layanan porter: $e');
|
||||
hasError.value = true;
|
||||
pesanError.value = 'Terjadi kesalahan saat memuat layanan porter.';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isTipeAktif(String tipe) {
|
||||
return tipe == 'arrival' || tipe == 'departure' || tipe == 'transit';
|
||||
}
|
||||
}
|
|
@ -1,32 +1,53 @@
|
|||
import 'package:e_porter/_core/constants/typography.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:zoom_tap_animation/zoom_tap_animation.dart';
|
||||
|
||||
import '../../../../_core/constants/colors.dart';
|
||||
|
||||
class CardSeat extends StatelessWidget {
|
||||
// final VoidCallback onTap;
|
||||
final bool isTaken;
|
||||
final bool isSelected;
|
||||
final String seatLabel;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const CardSeat({
|
||||
Key? key,
|
||||
// required this.onTap,
|
||||
required this.isTaken,
|
||||
required this.isSelected,
|
||||
required this.seatLabel,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
ZoomTapAnimation(
|
||||
Color backgroundColor;
|
||||
Border? border;
|
||||
if (isTaken) {
|
||||
backgroundColor = const Color(0xFFD9D9D9);
|
||||
} else if (isSelected) {
|
||||
backgroundColor = PrimaryColors.primary800;
|
||||
} else {
|
||||
backgroundColor = Colors.white;
|
||||
border = Border.all(width: 1.w, color: PrimaryColors.primary800);
|
||||
}
|
||||
|
||||
return ZoomTapAnimation(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
width: 40.w,
|
||||
height: 40.h,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.r),
|
||||
color: Color(0xFFD9D9D9),
|
||||
border: border,
|
||||
color: backgroundColor,
|
||||
),
|
||||
child: Center(
|
||||
child: TypographyStyles.body(seatLabel, color: Colors.white, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class FooterPrice extends StatelessWidget {
|
|||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "Rp ${price}",
|
||||
text: price,
|
||||
style: TextStyle(
|
||||
fontFamily: 'DMSans',
|
||||
color: PrimaryColors.primary800,
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
||||
import '../../../../_core/constants/colors.dart';
|
||||
import '../../../../_core/constants/typography.dart';
|
||||
|
||||
class PorterRadio extends StatelessWidget {
|
||||
final String title;
|
||||
final String subTitle;
|
||||
final String price;
|
||||
final String value;
|
||||
final String groupValue;
|
||||
final ValueChanged<String?> onTap;
|
||||
|
||||
const PorterRadio({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.subTitle,
|
||||
required this.price,
|
||||
required this.value,
|
||||
required this.groupValue,
|
||||
required this.onTap,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onTap,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
|
||||
child: Row(
|
||||
children: [
|
||||
Radio<String>(
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
onChanged: onTap,
|
||||
activeColor: PrimaryColors.primary800,
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TypographyStyles.body(
|
||||
title,
|
||||
color: GrayColors.gray800,
|
||||
maxlines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(height: 2.h),
|
||||
TypographyStyles.caption(
|
||||
subTitle,
|
||||
color: GrayColors.gray500,
|
||||
fontWeight: FontWeight.w400,
|
||||
maxlines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
TypographyStyles.caption(price, color: PrimaryColors.primary800)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,13 @@
|
|||
// ignore_for_file: unnecessary_null_comparison
|
||||
|
||||
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/component/icons/icons_library.dart';
|
||||
import 'package:e_porter/_core/constants/colors.dart';
|
||||
import 'package:e_porter/_core/constants/typography.dart';
|
||||
import 'package:e_porter/_core/service/logger_service.dart';
|
||||
import 'package:e_porter/domain/models/ticket_model.dart';
|
||||
import 'package:e_porter/presentation/controllers/ticket_controller.dart';
|
||||
import 'package:e_porter/presentation/screens/home/component/card_indicator.dart';
|
||||
import 'package:e_porter/presentation/screens/home/component/card_seat.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -12,7 +17,7 @@ import 'package:get/get.dart';
|
|||
import 'package:zoom_tap_animation/zoom_tap_animation.dart';
|
||||
|
||||
import '../../../../_core/component/button/button_fill.dart';
|
||||
import '../../routes/app_rountes.dart';
|
||||
import '../../../../domain/models/user_entity.dart';
|
||||
|
||||
class ChooseSeatScreen extends StatefulWidget {
|
||||
const ChooseSeatScreen({super.key});
|
||||
|
@ -23,19 +28,57 @@ class ChooseSeatScreen extends StatefulWidget {
|
|||
|
||||
class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
||||
final List<bool> selectedSeats = List.generate(3, (_) => false);
|
||||
// late ScrollController _scrollController;
|
||||
final Map<String, bool> selectedSeatsMap = {};
|
||||
late List<String> selectedSeatNumbers;
|
||||
int currentPassenger = 0;
|
||||
late final String ticketId;
|
||||
late final String flightId;
|
||||
late Future<FlightModel> _flightFuture;
|
||||
late final int passenger;
|
||||
late final List<PassengerModel?> selectedPassengers;
|
||||
late final TicketController ticketController;
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// _scrollController = ScrollController();
|
||||
// }
|
||||
String? cityDeparture;
|
||||
String? cityArrival;
|
||||
|
||||
// @override
|
||||
// void dispose() {
|
||||
// _scrollController.dispose();
|
||||
// super.dispose();
|
||||
// }
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final args = Get.arguments as Map<String, dynamic>;
|
||||
ticketId = args['ticketId'];
|
||||
flightId = args['flightId'];
|
||||
passenger = args['passenger'] ?? '';
|
||||
selectedPassengers = args['selectedPassenger'] ?? [];
|
||||
|
||||
ticketController = Get.find<TicketController>();
|
||||
_flightFuture = ticketController.getFlightById(ticketId: ticketId, flightId: flightId);
|
||||
|
||||
_loadFlightData();
|
||||
|
||||
selectedSeatNumbers = List.filled(passenger, '');
|
||||
if (passenger > 0) {
|
||||
currentPassenger = 0;
|
||||
for (int i = 0; i < selectedSeats.length; i++) {
|
||||
selectedSeats[i] = false;
|
||||
}
|
||||
selectedSeats[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void _loadFlightData() async {
|
||||
try {
|
||||
final flight = await ticketController.getFlightById(ticketId: ticketId, flightId: flightId);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
cityDeparture = flight.cityDeparture;
|
||||
cityArrival = flight.cityArrival;
|
||||
_flightFuture = Future.value(flight);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logger.e('Error loading flight data: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -44,15 +87,81 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
appBar: CustomeAppbarComponent(
|
||||
valueDari: 'Pilih Kursi',
|
||||
valueKe: null,
|
||||
date: 'Yogyakarta - Lombok',
|
||||
passenger: '2',
|
||||
date: '$cityDeparture - $cityArrival',
|
||||
passenger: '$passenger',
|
||||
onTab: () {
|
||||
Get.back();
|
||||
},
|
||||
),
|
||||
body: SafeArea(
|
||||
body: FutureBuilder<FlightModel>(
|
||||
future: _flightFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Center(child: Text("Error: ${snapshot.error}"));
|
||||
} else if (!snapshot.hasData) {
|
||||
return Center(child: Text("Data tidak ditemukan"));
|
||||
}
|
||||
|
||||
final flight = snapshot.data!;
|
||||
final codeDeparture = flight.codeDeparture;
|
||||
final codeArrival = flight.codeArrival;
|
||||
final codeTransit = flight.codeTransit;
|
||||
final flightClass = flight.flightClass;
|
||||
cityDeparture = flight.cityDeparture;
|
||||
cityArrival = flight.cityArrival;
|
||||
|
||||
Duration diff = flight.arrivalTime.difference(flight.departureTime);
|
||||
if (diff.isNegative) {
|
||||
diff = diff + Duration(days: 1);
|
||||
}
|
||||
final hours = diff.inHours;
|
||||
final minutes = diff.inMinutes % 60;
|
||||
final duration = '${hours}j ${minutes}m';
|
||||
|
||||
String stopText = '';
|
||||
if (flight.stop != null && flight.stop.isNotEmpty) {
|
||||
stopText = '${flight.stop} - ';
|
||||
}
|
||||
final finalDuration = '$stopText$duration';
|
||||
|
||||
// Ambil data seat per kolom
|
||||
final seatInfoA = flight.seat['a'];
|
||||
final seatInfoB = flight.seat['b'];
|
||||
final seatInfoC = flight.seat['c'];
|
||||
final seatInfoD = flight.seat['d'];
|
||||
final seatInfoE = flight.seat['e'];
|
||||
final seatInfoF = flight.seat['f'];
|
||||
|
||||
// Ambil totalSeat per kolom
|
||||
final rowCountA = seatInfoA?.totalSeat ?? 0;
|
||||
final rowCountB = seatInfoB?.totalSeat ?? 0;
|
||||
final rowCountC = seatInfoC?.totalSeat ?? 0;
|
||||
final rowCountD = seatInfoD?.totalSeat ?? 0;
|
||||
final rowCountE = seatInfoE?.totalSeat ?? 0;
|
||||
final rowCountF = seatInfoF?.totalSeat ?? 0;
|
||||
|
||||
// Ambil isTaken per kolom
|
||||
final seatA = seatInfoA?.isTaken ?? [];
|
||||
final seatB = seatInfoB?.isTaken ?? [];
|
||||
final seatC = seatInfoC?.isTaken ?? [];
|
||||
final seatD = seatInfoD?.isTaken ?? [];
|
||||
final seatE = seatInfoE?.isTaken ?? [];
|
||||
final seatF = seatInfoF?.isTaken ?? [];
|
||||
|
||||
// Jika ingin menentukan maxRows untuk nomor baris di tengah (misalnya)
|
||||
final int maxRows = [
|
||||
rowCountA,
|
||||
rowCountB,
|
||||
rowCountC,
|
||||
rowCountD,
|
||||
rowCountE,
|
||||
rowCountF,
|
||||
].reduce((a, b) => a > b ? a : b);
|
||||
|
||||
return SafeArea(
|
||||
child: CustomScrollView(
|
||||
// controller: _scrollController,
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
floating: true,
|
||||
|
@ -63,24 +172,28 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
expandedHeight: 220.h,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 0.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildCardFlight("YIA", "LOP", "Economy", "5j 40m"),
|
||||
_buildCardFlight(codeDeparture, codeTransit, codeArrival, flightClass, finalDuration),
|
||||
SizedBox(height: 20.h),
|
||||
SizedBox(
|
||||
height: 84.h,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w),
|
||||
padding: EdgeInsets.only(left: 16.w),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: 3,
|
||||
itemCount: selectedPassengers.length,
|
||||
itemBuilder: (context, index) {
|
||||
final passengerData = selectedPassengers[index];
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(right: 16.w),
|
||||
child: _buildPassengerCard('Ahmad', 'Economy', '10F', index),
|
||||
child: _buildPassengerCard(
|
||||
passengerData?.name ?? "Penumpang ${index + 1}",
|
||||
flightClass,
|
||||
'',
|
||||
index,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -91,7 +204,6 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: _SliverHeaderDelegate(
|
||||
|
@ -108,35 +220,41 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
SliverToBoxAdapter(
|
||||
child: CustomeShadowCotainner(
|
||||
sizeRadius: 0.r,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildCardSeat(context, label: 'A'),
|
||||
_buildCardSeat(context, label: 'B'),
|
||||
_buildCardSeat(context, label: 'C'),
|
||||
_buildSeatColumn("A", seatA, rowCountA),
|
||||
_buildSeatColumn("B", seatB, rowCountB),
|
||||
_buildSeatColumn("C", seatC, rowCountC),
|
||||
SizedBox(width: 16.w),
|
||||
_buildNumberSeat(context, label: '1'),
|
||||
_buildCardSeat(context, label: 'D'),
|
||||
_buildCardSeat(context, label: 'E'),
|
||||
_buildCardSeat(context, label: 'F'),
|
||||
],
|
||||
)
|
||||
_buildRowNumbers(maxRows),
|
||||
SizedBox(width: 16.w),
|
||||
_buildSeatColumn("D", seatD, rowCountD),
|
||||
_buildSeatColumn("E", seatE, rowCountE),
|
||||
_buildSeatColumn("F", seatF, rowCountF),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: CustomeShadowCotainner(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
||||
child: ZoomTapAnimation(
|
||||
child: ButtonFill(
|
||||
text: 'Lanjutkan',
|
||||
textColor: Colors.white,
|
||||
backgroundColor:
|
||||
selectedSeatNumbers.any((seat) => seat.isEmpty) ? GrayColors.gray400 : PrimaryColors.primary800,
|
||||
onTap: () {
|
||||
Get.toNamed(Routes.TICKETBOOKINGSTEP2);
|
||||
if (selectedSeatNumbers.any((seat) => seat.isEmpty)) {
|
||||
return;
|
||||
}
|
||||
logger.d('Kursi: $selectedSeatNumbers');
|
||||
Get.back(result: selectedSeatNumbers);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -144,7 +262,7 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildCardFlight(String departureCode, String arrivalCode, String kelas, String duration) {
|
||||
Widget _buildCardFlight(String departureCode, String transitCode, String arrivalCode, String kelas, String duration) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 20.h),
|
||||
child: CustomeShadowCotainner(
|
||||
|
@ -157,31 +275,27 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
children: [
|
||||
Row(
|
||||
children: [
|
||||
TypographyStyles.body(departureCode, color: GrayColors.gray800, letterSpacing: 0.2),
|
||||
TypographyStyles.body(departureCode, color: GrayColors.gray800),
|
||||
SizedBox(width: 10.w),
|
||||
CustomeIcons.RightOutline(color: GrayColors.gray800, size: 14),
|
||||
SizedBox(width: 10.w),
|
||||
TypographyStyles.body(arrivalCode, color: GrayColors.gray800, letterSpacing: 0.2),
|
||||
if (transitCode != null && transitCode.isNotEmpty) ...[
|
||||
TypographyStyles.body(transitCode, color: GrayColors.gray800),
|
||||
SizedBox(width: 10.w),
|
||||
CustomeIcons.RightOutline(color: GrayColors.gray800, size: 14),
|
||||
SizedBox(width: 10.w),
|
||||
],
|
||||
TypographyStyles.body(arrivalCode, color: GrayColors.gray800),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Row(
|
||||
children: [
|
||||
TypographyStyles.small(
|
||||
kelas,
|
||||
color: GrayColors.gray600,
|
||||
letterSpacing: 0.2,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
TypographyStyles.small(kelas, color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||
SizedBox(width: 10.w),
|
||||
CircleAvatar(radius: 2.r, backgroundColor: Color(0xFFD9D9D9)),
|
||||
SizedBox(width: 10.w),
|
||||
TypographyStyles.small(
|
||||
duration,
|
||||
color: GrayColors.gray600,
|
||||
letterSpacing: 0.2,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
TypographyStyles.small(duration, color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -201,6 +315,7 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
selectedSeats[i] = false;
|
||||
}
|
||||
selectedSeats[index] = true;
|
||||
currentPassenger = index;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
|
@ -223,7 +338,7 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
),
|
||||
SizedBox(height: 2.h),
|
||||
TypographyStyles.caption(
|
||||
'${kelas} / Kursi ${seat}',
|
||||
'${kelas} / Kursi ${selectedSeatNumbers[index].isEmpty ? '-' : selectedSeatNumbers[index]}',
|
||||
color: GrayColors.gray600,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
|
@ -257,27 +372,83 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildCardSeat(
|
||||
BuildContext context, {
|
||||
required label,
|
||||
}) {
|
||||
// Widget _buildCardSeat(
|
||||
// BuildContext context, {
|
||||
// required label,
|
||||
// }) {
|
||||
// return Expanded(
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Container(
|
||||
// width: 32.w,
|
||||
// height: 32.h,
|
||||
// child: TypographyStyles.body(label, color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
// ),
|
||||
// SizedBox(height: 6.h),
|
||||
// ListView.builder(
|
||||
// itemCount: 20,
|
||||
// shrinkWrap: true,
|
||||
// physics: NeverScrollableScrollPhysics(),
|
||||
// itemBuilder: (context, index) {
|
||||
// return Padding(
|
||||
// padding: EdgeInsets.symmetric(vertical: 6.h),
|
||||
// child: CardSeat(),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _buildSeatColumn(String column, List<bool> seatList, int totalSeat) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body(label, color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
alignment: Alignment.center,
|
||||
child: TypographyStyles.body(
|
||||
column,
|
||||
color: GrayColors.gray800,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 6.h),
|
||||
ListView.builder(
|
||||
itemCount: 20,
|
||||
shrinkWrap: true,
|
||||
itemCount: totalSeat,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final seatKey = '$column${index + 1}';
|
||||
bool taken = false;
|
||||
if (index < seatList.length) {
|
||||
taken = seatList[index];
|
||||
}
|
||||
bool isSelected = selectedSeatsMap[seatKey] ?? false;
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 6.h),
|
||||
child: CardSeat(),
|
||||
padding: EdgeInsets.only(top: 6.h, bottom: 6.h, right: 10.w),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (!taken) {
|
||||
setState(() {
|
||||
final oldSeatKey = selectedSeatNumbers[currentPassenger];
|
||||
if (oldSeatKey.isNotEmpty) {
|
||||
selectedSeatsMap[oldSeatKey] = false;
|
||||
}
|
||||
|
||||
selectedSeatsMap[seatKey] = true;
|
||||
selectedSeatNumbers[currentPassenger] = seatKey;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: CardSeat(
|
||||
isTaken: taken,
|
||||
isSelected: isSelected,
|
||||
seatLabel: seatKey,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -286,79 +457,26 @@ class _ChooseSeatScreenState extends State<ChooseSeatScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildNumberSeat(
|
||||
BuildContext context, {
|
||||
required label,
|
||||
}) {
|
||||
Widget _buildRowNumbers(int total) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body('', color: Colors.white, fontWeight: FontWeight.w500),
|
||||
),
|
||||
SizedBox(height: 6.h),
|
||||
ListView.builder(
|
||||
itemCount: 20,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
children: List.generate(
|
||||
total,
|
||||
(index) => Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 6.h),
|
||||
child: Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body(label, color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
width: 40.w,
|
||||
height: 40.h,
|
||||
alignment: Alignment.center,
|
||||
child: TypographyStyles.body(
|
||||
'${index + 1}',
|
||||
color: GrayColors.gray800,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNumber() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body('A', color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body('B', color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body('C', color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
),
|
||||
SizedBox(width: 106.w),
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body('D', color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body('E', color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
),
|
||||
SizedBox(width: 10.w),
|
||||
Container(
|
||||
width: 32.w,
|
||||
height: 32.h,
|
||||
child: TypographyStyles.body('F', color: GrayColors.gray800, fontWeight: FontWeight.w500),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,18 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
dynamic _loggedUser;
|
||||
List<PassengerModel?> selectedPassengers = [];
|
||||
|
||||
String? departureTime;
|
||||
String? arrivalTime;
|
||||
String? cityDeparture;
|
||||
String? cityArrival;
|
||||
String? airLines;
|
||||
String? code;
|
||||
String? flightClass;
|
||||
String? transitAirplane;
|
||||
String? stop;
|
||||
String? codeDeparture;
|
||||
String? codeArrival;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -61,6 +73,15 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
selectedPassengers = List.filled(passenger, null, growable: false);
|
||||
}
|
||||
|
||||
PassengerModel _convertUserDataToPassengerModel(UserData userData) {
|
||||
return PassengerModel(
|
||||
name: userData.name ?? '',
|
||||
typeId: userData.tipeId ?? '',
|
||||
noId: userData.noId ?? '',
|
||||
gender: userData.gender ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadPassengers() async {
|
||||
final userData = await PreferencesService.getUserData();
|
||||
if (userData == null || userData.uid.isEmpty) {
|
||||
|
@ -103,8 +124,17 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
}
|
||||
final flight = snapshot.data!;
|
||||
|
||||
final departureTime = DateFormat.jm().format(flight.departureTime);
|
||||
final arrivalTime = DateFormat.jm().format(flight.arrivalTime);
|
||||
departureTime = DateFormat.jm().format(flight.departureTime);
|
||||
arrivalTime = DateFormat.jm().format(flight.arrivalTime);
|
||||
cityDeparture = flight.cityDeparture;
|
||||
cityArrival = flight.cityArrival;
|
||||
airLines = flight.airLines;
|
||||
code = flight.code;
|
||||
flightClass = flight.flightClass;
|
||||
transitAirplane = flight.transitAirplane;
|
||||
stop = flight.stop;
|
||||
codeDeparture = flight.codeDeparture;
|
||||
codeArrival = flight.codeArrival;
|
||||
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
|
@ -116,13 +146,13 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
CardFlightInformation(
|
||||
date: ticketDate,
|
||||
time: '$departureTime - $arrivalTime',
|
||||
departureCity: flight.cityDeparture,
|
||||
arrivalCity: flight.cityArrival,
|
||||
plane: '${flight.airLines} (${flight.code})',
|
||||
seatClass: flight.flightClass,
|
||||
departureCity: '$cityDeparture',
|
||||
arrivalCity: '$cityArrival',
|
||||
plane: '${airLines} (${code})',
|
||||
seatClass: '$flightClass',
|
||||
passenger: '$passenger',
|
||||
transiAirplane: flight.transitAirplane,
|
||||
stop: flight.stop,
|
||||
transiAirplane: '$transitAirplane',
|
||||
stop: '$stop',
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
TypographyStyles.h6('Detail Pemesanan', color: GrayColors.gray800),
|
||||
|
@ -146,10 +176,30 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
textColor: Colors.white,
|
||||
backgroundColor: isAllPassengersFilled() ? PrimaryColors.primary800 : GrayColors.gray400,
|
||||
onTap: () {
|
||||
if (!isAllPassengersFilled()) {
|
||||
logger.d('Selected Passengers: $selectedPassengers');
|
||||
if (selectedPassengers.any((p) => p == null)) {
|
||||
SnackbarHelper.showError('Error', 'Harap lengkapi slot penumpang');
|
||||
} else {
|
||||
Get.toNamed(Routes.TICKETBOOKINGSTEP2);
|
||||
final argument = {
|
||||
'ticketId': ticketId,
|
||||
'flightId': flightId,
|
||||
'date': ticketDate,
|
||||
'departureTime': departureTime,
|
||||
'arrivalTime': arrivalTime,
|
||||
'cityDeparture': cityDeparture,
|
||||
'cityArrival': cityArrival,
|
||||
'airLines': airLines,
|
||||
'code': code,
|
||||
'flightClass': flightClass,
|
||||
'transitAirplane': transitAirplane,
|
||||
'stop': stop,
|
||||
'codeDeparture': codeDeparture,
|
||||
'codeArrival': codeArrival,
|
||||
'passenger': passenger,
|
||||
'selectedPassenger': selectedPassengers,
|
||||
};
|
||||
|
||||
Get.toNamed(Routes.TICKETBOOKINGSTEP2, arguments: argument);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -201,11 +251,14 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('isPassengerAdd', newValue);
|
||||
|
||||
setState(
|
||||
() {
|
||||
setState(() {
|
||||
isToggled = newValue;
|
||||
},
|
||||
);
|
||||
if (!newValue) {
|
||||
selectedPassengers[0] = null;
|
||||
} else if (_loggedUser != null) {
|
||||
selectedPassengers[0] = _convertUserDataToPassengerModel(_loggedUser);
|
||||
}
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -233,7 +286,7 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
passenger,
|
||||
(index) {
|
||||
if (isToggled && index == 0 && _loggedUser != null) {
|
||||
return _buildUserPassengerCard(_loggedUser);
|
||||
return _buildUserPassengerCard(_loggedUser, index);
|
||||
} else {
|
||||
final p = selectedPassengers[index];
|
||||
if (p != null) {
|
||||
|
@ -247,7 +300,8 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildUserPassengerCard(dynamic user) {
|
||||
Widget _buildUserPassengerCard(dynamic user, int index) {
|
||||
bool isSlotEditTable = !isToggled || index != 0;
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 16.h),
|
||||
child: CustomeShadowCotainner(
|
||||
|
@ -270,6 +324,20 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
ZoomTapAnimation(
|
||||
child: IconButton(
|
||||
icon: CustomeIcons.RemoveOutline(),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
selectedPassengers[0] = null;
|
||||
isToggled = false;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
if (isSlotEditTable)
|
||||
ZoomTapAnimation(
|
||||
child: GestureDetector(
|
||||
child: CustomeIcons.EditOutline(),
|
||||
|
@ -340,6 +408,8 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
|
@ -370,6 +440,18 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
ZoomTapAnimation(
|
||||
child: IconButton(
|
||||
icon: CustomeIcons.RemoveOutline(),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
selectedPassengers[slotIndex] = null;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
ZoomTapAnimation(
|
||||
child: GestureDetector(
|
||||
child: CustomeIcons.EditOutline(),
|
||||
|
@ -377,6 +459,8 @@ class _TicketBookingStep1ScreenState extends State<TicketBookingStep1Screen> {
|
|||
_onEditPassenger(slotIndex);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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';
|
||||
import 'package:e_porter/_core/service/logger_service.dart';
|
||||
import 'package:e_porter/domain/models/user_entity.dart';
|
||||
import 'package:e_porter/presentation/controllers/ticket_controller.dart';
|
||||
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
|
@ -19,6 +22,50 @@ class TicketBookingStep2Screen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _TicketBookingStep2ScreenState extends State<TicketBookingStep2Screen> {
|
||||
late final TicketController ticketController;
|
||||
late final String ticketId;
|
||||
late final String flightId;
|
||||
String? ticketDate;
|
||||
String? departureTime;
|
||||
String? arrivalTime;
|
||||
String? cityDeparture;
|
||||
String? cityArrival;
|
||||
String? airLines;
|
||||
String? code;
|
||||
String? flightClass;
|
||||
String? transitAirplane;
|
||||
String? stop;
|
||||
String? codeDeparture;
|
||||
String? codeArrival;
|
||||
late final int passenger;
|
||||
late final List<PassengerModel?> selectedPassengers;
|
||||
late List<String> selectedSeatNumbers;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final args = Get.arguments as Map<String, dynamic>;
|
||||
ticketId = args['ticketId'];
|
||||
flightId = args['flightId'];
|
||||
ticketDate = args['date'];
|
||||
departureTime = args['departureTime'];
|
||||
arrivalTime = args['arrivalTime'];
|
||||
cityDeparture = args['cityDeparture'];
|
||||
cityArrival = args['cityArrival'];
|
||||
airLines = args['airLines'];
|
||||
code = args['code'];
|
||||
flightClass = args['flightClass'];
|
||||
transitAirplane = args['transitAirplane'];
|
||||
stop = args['stop'];
|
||||
codeDeparture = args['codeDeparture'];
|
||||
codeArrival = args['codeArrival'];
|
||||
passenger = args['passenger'];
|
||||
selectedPassengers = args['selectedPassenger'] ?? [];
|
||||
selectedSeatNumbers = args['selectedSeatNumbers'] ?? List.filled(passenger, '');
|
||||
|
||||
logger.d('Ticket ID: $ticketId \nFlight ID: $flightId');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
@ -38,25 +85,47 @@ class _TicketBookingStep2ScreenState extends State<TicketBookingStep2Screen> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CardFlightInformation(
|
||||
date: 'Sen, 27 Jan 2025',
|
||||
time: '12.20 - 06.00 AM',
|
||||
departureCity: 'Yogyakarta',
|
||||
arrivalCity: 'Lombok',
|
||||
plane: 'Citilink (103)',
|
||||
seatClass: 'Economy',
|
||||
passenger: '2',
|
||||
date: '$ticketDate',
|
||||
time: '${departureTime} - ${arrivalTime}',
|
||||
departureCity: '$cityDeparture',
|
||||
arrivalCity: '$cityArrival',
|
||||
plane: '${airLines} (${code})',
|
||||
seatClass: '$flightClass',
|
||||
passenger: '$passenger',
|
||||
transiAirplane: '$transitAirplane',
|
||||
stop: '$stop',
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
TypographyStyles.h6('Pilih Kursi', color: GrayColors.gray800),
|
||||
SizedBox(height: 20.h),
|
||||
_buildCardSeatPessenger(
|
||||
SizedBox(height: 4.h),
|
||||
...List.generate(
|
||||
passenger,
|
||||
(index) {
|
||||
final passengerData = selectedPassengers[index];
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 16.h),
|
||||
child: _buildCardSeatPessenger(
|
||||
context,
|
||||
label: '1',
|
||||
namePassenger: 'AHMAD CHOIRUL UMAM ALI R',
|
||||
seatClass: 'Economy',
|
||||
numberSeat: '10F',
|
||||
onTap: () {
|
||||
Get.toNamed(Routes.CHOOSECHAIR);
|
||||
label: '${index + 1}',
|
||||
namePassenger: passengerData?.name ?? 'Unknown Passenger',
|
||||
seatClass: '$flightClass',
|
||||
numberSeat: '${selectedSeatNumbers[index].isEmpty ? '-' : selectedSeatNumbers[index]}',
|
||||
onTap: () async {
|
||||
final argument = {
|
||||
'ticketId': ticketId,
|
||||
'flightId': flightId,
|
||||
'passenger': passenger,
|
||||
'selectedPassenger': selectedPassengers,
|
||||
};
|
||||
final result = await Get.toNamed(Routes.CHOOSECHAIR, arguments: argument);
|
||||
if (result != null && result is List<String>) {
|
||||
setState(() {
|
||||
selectedSeatNumbers = result;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -70,8 +139,21 @@ class _TicketBookingStep2ScreenState extends State<TicketBookingStep2Screen> {
|
|||
child: ButtonFill(
|
||||
text: 'Lanjutkan',
|
||||
textColor: Colors.white,
|
||||
onTap: () {
|
||||
Get.toNamed(Routes.TICKETBOOKINGSTEP3);
|
||||
backgroundColor:
|
||||
selectedSeatNumbers.any((seat) => seat.isEmpty) ? GrayColors.gray400 : PrimaryColors.primary800,
|
||||
onTap: selectedSeatNumbers.any((seat) => seat.isEmpty)
|
||||
? null
|
||||
: () {
|
||||
final argument = {
|
||||
'ticketId': ticketId,
|
||||
'flightId': flightId,
|
||||
'date': ticketDate,
|
||||
'passenger': passenger,
|
||||
'selectedPassenger': selectedPassengers,
|
||||
'numberSeat': selectedSeatNumbers
|
||||
};
|
||||
logger.d('Number Seat: $selectedSeatNumbers \n Passenger: $selectedPassengers');
|
||||
Get.toNamed(Routes.TICKETBOOKINGSTEP3, arguments: argument);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -79,12 +161,14 @@ class _TicketBookingStep2ScreenState extends State<TicketBookingStep2Screen> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildCardSeatPessenger(BuildContext context,
|
||||
{required String label,
|
||||
Widget _buildCardSeatPessenger(
|
||||
BuildContext context, {
|
||||
required String label,
|
||||
required String namePassenger,
|
||||
required String seatClass,
|
||||
required String numberSeat,
|
||||
required VoidCallback onTap}) {
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return CustomeShadowCotainner(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
|
|
@ -2,12 +2,20 @@ import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
|
|||
import 'package:e_porter/_core/component/icons/icons_library.dart';
|
||||
import 'package:e_porter/_core/constants/colors.dart';
|
||||
import 'package:e_porter/_core/constants/typography.dart';
|
||||
import 'package:e_porter/_core/service/logger_service.dart';
|
||||
import 'package:e_porter/presentation/screens/home/component/footer_price.dart';
|
||||
import 'package:e_porter/presentation/screens/home/component/porter_radio.dart';
|
||||
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../../_core/component/appbar/appbar_component.dart';
|
||||
import '../../../../domain/models/porter_service_model.dart';
|
||||
import '../../../../domain/models/ticket_model.dart';
|
||||
import '../../../../domain/models/user_entity.dart';
|
||||
import '../../../controllers/porter_service_controller.dart';
|
||||
import '../../../controllers/ticket_controller.dart';
|
||||
import '../component/card_flight_information.dart';
|
||||
|
||||
class TicketBookingStep3Screen extends StatefulWidget {
|
||||
|
@ -18,12 +26,175 @@ class TicketBookingStep3Screen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _TicketBookingStep3ScreenState extends State<TicketBookingStep3Screen> {
|
||||
final ValueNotifier<String> selectedPorter1 = ValueNotifier<String>('');
|
||||
final ValueNotifier<String> selectedPorter2 = ValueNotifier<String>('');
|
||||
final ValueNotifier<String> selectedPorter3 = ValueNotifier<String>('');
|
||||
|
||||
Map<int, String> layananTipe = {1: 'departure', 2: 'arrival', 3: 'transit'};
|
||||
|
||||
bool _isChecked1 = false;
|
||||
bool _isChecked2 = false;
|
||||
bool _isChecked3 = false;
|
||||
|
||||
late final PorterServiceController _porterController;
|
||||
final TicketController ticketController = Get.find<TicketController>();
|
||||
late final String ticketId;
|
||||
late final String flightId;
|
||||
late String? ticketDate;
|
||||
late final int passenger;
|
||||
late final List<PassengerModel?> selectedPassengers;
|
||||
late List<String> numberSeat;
|
||||
|
||||
FlightModel? flightData;
|
||||
String? departureTime;
|
||||
String? arrivalTime;
|
||||
|
||||
double totalPriceService = 0.0;
|
||||
PorterServiceModel? selectedDepartureService;
|
||||
PorterServiceModel? selectedArrivalService;
|
||||
PorterServiceModel? selectedTransitService;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final args = Get.arguments as Map<String, dynamic>;
|
||||
ticketId = args['ticketId'];
|
||||
flightId = args['flightId'];
|
||||
ticketDate = args['date'];
|
||||
passenger = args['passenger'] ?? 0;
|
||||
selectedPassengers = args['selectedPassenger'] ?? [];
|
||||
numberSeat = args['numberSeat'];
|
||||
|
||||
_porterController = Get.find<PorterServiceController>();
|
||||
_porterController.fetchLayananPorter();
|
||||
|
||||
fetchDataFlight();
|
||||
}
|
||||
|
||||
Future<void> fetchDataFlight() async {
|
||||
try {
|
||||
FlightModel flight = await ticketController.getFlightById(ticketId: ticketId, flightId: flightId);
|
||||
setState(() {
|
||||
flightData = flight;
|
||||
departureTime = DateFormat.jm().format(flightData!.departureTime);
|
||||
arrivalTime = DateFormat.jm().format(flightData!.arrivalTime);
|
||||
});
|
||||
} catch (e) {
|
||||
logger.e('Terjadi kesalahan: $e');
|
||||
}
|
||||
}
|
||||
|
||||
double calculateTotalPrice(double ticketPrice, int passengerCount) {
|
||||
return ticketPrice * passengerCount;
|
||||
}
|
||||
|
||||
void updateServicePrice(double servicePrice, bool isSelected) {
|
||||
setState(() {
|
||||
if (isSelected) {
|
||||
totalPriceService += servicePrice;
|
||||
} else {
|
||||
totalPriceService -= servicePrice;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onCheckboxChanged(int checkboxNumber, bool? value) {
|
||||
setState(() {
|
||||
switch (checkboxNumber) {
|
||||
case 1:
|
||||
bool oldValue = _isChecked1;
|
||||
_isChecked1 = value ?? false;
|
||||
|
||||
if (oldValue && !_isChecked1 && selectedDepartureService != null) {
|
||||
totalPriceService -= selectedDepartureService!.price;
|
||||
selectedDepartureService = null;
|
||||
selectedPorter1.value = '';
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
bool oldValue = _isChecked2;
|
||||
_isChecked2 = value ?? false;
|
||||
|
||||
if (oldValue && !_isChecked2 && selectedArrivalService != null) {
|
||||
totalPriceService -= selectedArrivalService!.price;
|
||||
selectedArrivalService = null;
|
||||
selectedPorter2.value = '';
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
bool oldValue = _isChecked3;
|
||||
_isChecked3 = value ?? false;
|
||||
|
||||
if (oldValue && !_isChecked3 && selectedTransitService != null) {
|
||||
totalPriceService -= selectedTransitService!.price;
|
||||
selectedTransitService = null;
|
||||
selectedPorter3.value = '';
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _onPorterSelectionChanged(PorterServiceModel service, bool isSelected, String serviceType) {
|
||||
setState(() {
|
||||
if (serviceType == 'departure') {
|
||||
if (isSelected) {
|
||||
if (selectedDepartureService != null) {
|
||||
totalPriceService -= selectedDepartureService!.price;
|
||||
}
|
||||
totalPriceService += service.price;
|
||||
selectedDepartureService = service;
|
||||
} else {
|
||||
if (selectedDepartureService != null) {
|
||||
totalPriceService -= selectedDepartureService!.price;
|
||||
selectedDepartureService = null;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (serviceType == 'arrival') {
|
||||
if (isSelected) {
|
||||
if (selectedArrivalService != null) {
|
||||
totalPriceService -= selectedArrivalService!.price;
|
||||
}
|
||||
totalPriceService += service.price;
|
||||
selectedArrivalService = service;
|
||||
} else {
|
||||
if (selectedArrivalService != null) {
|
||||
totalPriceService -= selectedArrivalService!.price;
|
||||
selectedArrivalService = null;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (serviceType == 'transit') {
|
||||
if (isSelected) {
|
||||
if (selectedTransitService != null) {
|
||||
totalPriceService -= selectedTransitService!.price;
|
||||
}
|
||||
totalPriceService += service.price;
|
||||
selectedTransitService = service;
|
||||
} else {
|
||||
if (selectedTransitService != null) {
|
||||
totalPriceService -= selectedTransitService!.price;
|
||||
selectedTransitService = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
List<String> _getSelectedServices() {
|
||||
List<String> selectedServices = [];
|
||||
if (_isChecked1) selectedServices.add("Keberangkatan: Fast Track");
|
||||
if (_isChecked2) selectedServices.add("Kedatangan: Porter VIP");
|
||||
if (_isChecked3) selectedServices.add("Transit: Transit");
|
||||
return selectedServices;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double totalPrice = calculateTotalPrice(flightData?.price.toDouble() ?? 0.0, passenger);
|
||||
double grandTotal = totalPrice + totalPriceService;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: GrayColors.gray50,
|
||||
appBar: ProgressAppbarComponent(
|
||||
|
@ -41,13 +212,15 @@ class _TicketBookingStep3ScreenState extends State<TicketBookingStep3Screen> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
CardFlightInformation(
|
||||
date: "Sen, 27 Jan 2025",
|
||||
time: "12.20 - 06.00 AM",
|
||||
departureCity: "Yogyakarta",
|
||||
arrivalCity: "Lombok",
|
||||
plane: "Citilink (103)",
|
||||
seatClass: "Economy",
|
||||
passenger: "2",
|
||||
date: '$ticketDate',
|
||||
time: "${departureTime} - ${arrivalTime}",
|
||||
departureCity: '${flightData?.cityDeparture}',
|
||||
arrivalCity: "${flightData?.cityArrival}",
|
||||
plane: "${flightData?.airLines} (${flightData?.code})",
|
||||
seatClass: "${flightData?.flightClass}",
|
||||
passenger: "$passenger",
|
||||
stop: "${flightData?.stop}",
|
||||
transiAirplane: "${flightData?.transitAirplane}",
|
||||
),
|
||||
SizedBox(height: 32.h),
|
||||
TypographyStyles.h6("Layanan Porter", color: GrayColors.gray800),
|
||||
|
@ -61,46 +234,48 @@ class _TicketBookingStep3ScreenState extends State<TicketBookingStep3Screen> {
|
|||
SizedBox(height: 16.h),
|
||||
_buildCheckBox(
|
||||
context,
|
||||
label: "Kedatangan",
|
||||
Widget: CustomeIcons.AirplaneLandingOutline(color: Colors.white),
|
||||
label: "Keberangkatan",
|
||||
Widget: CustomeIcons.AirplaneTakeOffOutline(color: Colors.white),
|
||||
value: _isChecked1,
|
||||
onTap: (bool? value) {
|
||||
setState(() {
|
||||
_isChecked1 = value ?? false;
|
||||
});
|
||||
_onCheckboxChanged(1, value);
|
||||
},
|
||||
),
|
||||
if (_isChecked1)
|
||||
_buildPorterServicesList(selectedPorter: selectedPorter1, serviceType: layananTipe[1]!),
|
||||
SizedBox(height: 10.h),
|
||||
_buildCheckBox(
|
||||
context,
|
||||
label: "Keberangkatan",
|
||||
Widget: CustomeIcons.AirplaneTakeOffOutline(color: Colors.white),
|
||||
label: "Kedatangan",
|
||||
Widget: CustomeIcons.AirplaneLandingOutline(color: Colors.white),
|
||||
value: _isChecked2,
|
||||
onTap: (bool? value) {
|
||||
setState(() {
|
||||
_isChecked2 = value ?? false;
|
||||
});
|
||||
_onCheckboxChanged(2, value);
|
||||
},
|
||||
),
|
||||
if (_isChecked2)
|
||||
_buildPorterServicesList(selectedPorter: selectedPorter2, serviceType: layananTipe[2]!),
|
||||
SizedBox(height: 10.h),
|
||||
if (flightData?.stop != null && flightData!.stop.isNotEmpty) ...[
|
||||
_buildCheckBox(
|
||||
context,
|
||||
label: "Transit",
|
||||
Widget: CustomeIcons.TransitOutline(color: Colors.white),
|
||||
value: _isChecked3,
|
||||
onTap: (bool? value) {
|
||||
setState(() {
|
||||
_isChecked3 = value ?? false;
|
||||
});
|
||||
_onCheckboxChanged(3, value);
|
||||
},
|
||||
),
|
||||
if (_isChecked3)
|
||||
_buildPorterServicesList(selectedPorter: selectedPorter3, serviceType: layananTipe[3]!),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: FooterPrice(
|
||||
price: "1.450.000",
|
||||
price: "Rp ${NumberFormat.decimalPattern('id_ID').format(grandTotal)}",
|
||||
labelText: "Pesanan",
|
||||
labelButton: "Lanjut",
|
||||
onTap: () {
|
||||
|
@ -141,4 +316,96 @@ class _TicketBookingStep3ScreenState extends State<TicketBookingStep3Screen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPorterServicesList({
|
||||
required ValueNotifier<String> selectedPorter,
|
||||
required String serviceType,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 8.h, bottom: 8.h, left: 32.w, right: 0),
|
||||
child: ValueListenableBuilder<String>(
|
||||
valueListenable: selectedPorter,
|
||||
builder: (context, selectedValue, child) {
|
||||
return Obx(() {
|
||||
if (_porterController.isLoading.value) {
|
||||
return Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: PrimaryColors.primary800,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_porterController.hasError.value) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(8.h),
|
||||
child: Text(
|
||||
'Terjadi kesalahan: ${_porterController.pesanError.value}',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<PorterServiceModel> filteredServices = [];
|
||||
if (serviceType == 'departure') {
|
||||
filteredServices = _porterController.layananPorterArrival;
|
||||
} else if (serviceType == 'arrival') {
|
||||
filteredServices = _porterController.layananPorterDeparture;
|
||||
} else if (serviceType == 'transit') {
|
||||
filteredServices = _porterController.layananPorterTransit;
|
||||
}
|
||||
|
||||
if (filteredServices.isEmpty) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(8.h),
|
||||
child: Text(
|
||||
'Tidak ada layanan porter untuk ${_getTipeLabel(serviceType)}',
|
||||
style: TextStyle(color: GrayColors.gray600),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemCount: filteredServices.length,
|
||||
itemBuilder: (context, index) {
|
||||
final service = filteredServices[index];
|
||||
return Column(
|
||||
children: [
|
||||
PorterRadio(
|
||||
title: service.name,
|
||||
subTitle: service.description,
|
||||
price: 'Rp ${NumberFormat.decimalPattern('id_ID').format(service.price)}',
|
||||
value: service.id ?? '',
|
||||
groupValue: selectedValue,
|
||||
onTap: (value) {
|
||||
selectedPorter.value = value!;
|
||||
_onPorterSelectionChanged(service, selectedPorter.value.isNotEmpty, serviceType);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 10.h),
|
||||
Divider(thickness: 1, color: GrayColors.gray200),
|
||||
SizedBox(height: 10.h),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getTipeLabel(String serviceType) {
|
||||
switch (serviceType) {
|
||||
case 'departure':
|
||||
return 'Keberangkatan';
|
||||
case 'arrival':
|
||||
return 'Kedatangan';
|
||||
case 'transit':
|
||||
return 'Transit';
|
||||
default:
|
||||
return serviceType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,13 @@ import 'package:e_porter/presentation/screens/home/component/profile_avatar.dart
|
|||
import 'package:e_porter/presentation/screens/profile/component/profile_menu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:zoom_tap_animation/zoom_tap_animation.dart';
|
||||
|
||||
import '../../../../_core/constants/colors.dart';
|
||||
import '../../../../_core/service/preferences_service.dart';
|
||||
import '../../../../domain/models/user_entity.dart';
|
||||
import '../../routes/app_rountes.dart';
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
const ProfileScreen({super.key});
|
||||
|
@ -16,12 +21,38 @@ class ProfileScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _ProfileScreenState extends State<ProfileScreen> {
|
||||
late final String role;
|
||||
late Future<UserData?> _userDataFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
role = Get.arguments ?? 'penumpang';
|
||||
_userDataFuture = PreferencesService.getUserData();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (role == 'porter') {
|
||||
return _buildPorterUI();
|
||||
}
|
||||
return _buildPassengerUI();
|
||||
}
|
||||
|
||||
Widget _buildPassengerUI() {
|
||||
return Scaffold(
|
||||
backgroundColor: GrayColors.gray50,
|
||||
appBar: BasicAppbarComponent(title: 'Profil'),
|
||||
body: SafeArea(
|
||||
body: FutureBuilder<UserData?>(
|
||||
future: _userDataFuture,
|
||||
builder: (context, snapshot) {
|
||||
String userName = "Guest";
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasData && snapshot.data?.name != null) {
|
||||
userName = snapshot.data!.name!;
|
||||
}
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 20.h),
|
||||
child: SingleChildScrollView(
|
||||
|
@ -31,13 +62,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
borderRadius: BorderRadius.circular(0.r),
|
||||
child: Row(
|
||||
children: [
|
||||
ProfileAvatar(fullName: 'fullName'),
|
||||
ProfileAvatar(fullName: userName),
|
||||
SizedBox(width: 16.w),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
TypographyStyles.caption('Hi,', color: GrayColors.gray600, fontWeight: FontWeight.w400),
|
||||
TypographyStyles.body('Muhammad Al Kahfi', color: GrayColors.gray800),
|
||||
TypographyStyles.body(userName, color: GrayColors.gray800),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -81,7 +112,53 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
ProfileMenu(
|
||||
label: 'Logout',
|
||||
svgIcon: 'assets/icons/ic_logout.svg',
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
backgroundColor: Colors.white,
|
||||
title: TypographyStyles.body('Keluar', color: GrayColors.gray800),
|
||||
content: TypographyStyles.caption(
|
||||
'Apakah anda yakin untuk keluar dari akun ini?',
|
||||
color: GrayColors.gray600,
|
||||
fontWeight: FontWeight.w500,
|
||||
maxlines: 3,
|
||||
),
|
||||
actions: [
|
||||
ZoomTapAnimation(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: TypographyStyles.caption(
|
||||
'Tidak',
|
||||
color: GrayColors.gray800,
|
||||
),
|
||||
),
|
||||
),
|
||||
ZoomTapAnimation(
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
await PreferencesService.clearUserData();
|
||||
Navigator.of(context).pop();
|
||||
Get.offAllNamed(Routes.SPLASH);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 30.w, vertical: 8.h),
|
||||
decoration: BoxDecoration(
|
||||
color: PrimaryColors.primary800,
|
||||
borderRadius: BorderRadius.circular(8.r),
|
||||
),
|
||||
child: TypographyStyles.caption('Ya', color: Colors.white),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -90,7 +167,13 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPorterUI() {
|
||||
return Scaffold();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:e_porter/domain/bindings/auth_binding.dart';
|
||||
import 'package:e_porter/domain/bindings/navigation_binding.dart';
|
||||
import 'package:e_porter/domain/bindings/porter_service_binding.dart';
|
||||
import 'package:e_porter/domain/bindings/profil_binding.dart';
|
||||
import 'package:e_porter/domain/bindings/search_flight_binding.dart';
|
||||
import 'package:e_porter/domain/bindings/ticket_binding.dart';
|
||||
|
@ -105,6 +106,7 @@ class AppRoutes {
|
|||
GetPage(
|
||||
name: Routes.TICKETBOOKINGSTEP3,
|
||||
page: () => TicketBookingStep3Screen(),
|
||||
binding: PorterServiceBinding(),
|
||||
),
|
||||
GetPage(
|
||||
name: Routes.TICKETBOOKINGSTEP4,
|
||||
|
|
|
@ -47,7 +47,7 @@ dependencies:
|
|||
firebase_core: ^3.11.0
|
||||
cloud_firestore: ^5.6.4
|
||||
firebase_auth: ^5.5.1
|
||||
shared_preferences: ^2.5.2
|
||||
shared_preferences: ^2.4.6
|
||||
logger: ^2.5.0
|
||||
dropdown_button2: ^2.3.9
|
||||
google_fonts: ^6.2.1
|
||||
|
|
Loading…
Reference in New Issue