Feat: done logic search bar
This commit is contained in:
parent
bd52aed744
commit
381f4f6a8f
|
@ -434,7 +434,7 @@
|
||||||
"languageVersion": "3.4"
|
"languageVersion": "3.4"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"generated": "2025-03-05T07:39:52.656986Z",
|
"generated": "2025-03-07T08:39:17.979426Z",
|
||||||
"generator": "pub",
|
"generator": "pub",
|
||||||
"generatorVersion": "3.5.0",
|
"generatorVersion": "3.5.0",
|
||||||
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,4 @@
|
||||||
#Wed Mar 05 14:40:52 WIB 2025
|
#Fri Mar 07 15:39:47 WIB 2025
|
||||||
com.example.e_porter.app-main-46\:/drawable-v21/launch_background.xml=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\merged_res\\debug\\drawable-v21_launch_background.xml.flat
|
com.example.e_porter.app-main-46\:/drawable-v21/launch_background.xml=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\merged_res\\debug\\drawable-v21_launch_background.xml.flat
|
||||||
com.example.e_porter.app-main-46\:/mipmap-hdpi/ic_launcher.png=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\merged_res\\debug\\mipmap-hdpi_ic_launcher.png.flat
|
com.example.e_porter.app-main-46\:/mipmap-hdpi/ic_launcher.png=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\merged_res\\debug\\mipmap-hdpi_ic_launcher.png.flat
|
||||||
com.example.e_porter.app-main-46\:/mipmap-mdpi/ic_launcher.png=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\merged_res\\debug\\mipmap-mdpi_ic_launcher.png.flat
|
com.example.e_porter.app-main-46\:/mipmap-mdpi/ic_launcher.png=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\merged_res\\debug\\mipmap-mdpi_ic_launcher.png.flat
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#Thu Mar 06 15:36:28 WIB 2025
|
#Sat Mar 08 16:02:32 WIB 2025
|
||||||
base.0=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeExtDexDebug\\classes.dex
|
base.0=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeExtDexDebug\\classes.dex
|
||||||
base.1=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeLibDexDebug\\0\\classes.dex
|
base.1=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeLibDexDebug\\0\\classes.dex
|
||||||
base.2=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeProjectDexDebug\\0\\classes.dex
|
base.2=D\:\\Flutter\\Flutter Project\\e_porter\\build\\app\\intermediates\\dex\\debug\\mergeProjectDexDebug\\0\\classes.dex
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -5,10 +5,12 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
class SearchBarComponent extends StatelessWidget {
|
class SearchBarComponent extends StatelessWidget {
|
||||||
final String hintText;
|
final String hintText;
|
||||||
|
final ValueChanged<String>? onChanged;
|
||||||
|
|
||||||
const SearchBarComponent({
|
const SearchBarComponent({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
|
this.onChanged
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -18,6 +20,7 @@ class SearchBarComponent extends StatelessWidget {
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
onChanged: onChanged,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: Padding(
|
prefixIcon: Padding(
|
||||||
padding: EdgeInsets.only(left: 16.w, right: 11.w),
|
padding: EdgeInsets.only(left: 16.w, right: 11.w),
|
||||||
|
|
|
@ -26,6 +26,7 @@ class PreferencesService {
|
||||||
final now = DateTime.now().millisecondsSinceEpoch;
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
if (now > expiredAt) {
|
if (now > expiredAt) {
|
||||||
await clearUserData();
|
await clearUserData();
|
||||||
|
print("now: $now, expiredAt: $expiredAt");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ class PreferencesService {
|
||||||
final userData = UserData.fromMap(map);
|
final userData = UserData.fromMap(map);
|
||||||
return userData;
|
return userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> clearUserData() async {
|
static Future<void> clearUserData() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
await prefs.remove(_userDataKey);
|
await prefs.remove(_userDataKey);
|
||||||
|
|
|
@ -3,6 +3,11 @@ import 'package:e_porter/domain/models/user_entity.dart';
|
||||||
import 'package:e_porter/domain/repositories/auth_repository.dart';
|
import 'package:e_porter/domain/repositories/auth_repository.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
|
||||||
|
class AuthException implements Exception {
|
||||||
|
final String message;
|
||||||
|
AuthException(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
class AuthRepositoryImpl implements AuthRepository {
|
class AuthRepositoryImpl implements AuthRepository {
|
||||||
final FirebaseAuth _firebaseAuth;
|
final FirebaseAuth _firebaseAuth;
|
||||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
@ -19,7 +24,21 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||||
final user = userCredential.user!;
|
final user = userCredential.user!;
|
||||||
return UserEntity(uid: user.uid, email: user.email ?? "");
|
return UserEntity(uid: user.uid, email: user.email ?? "");
|
||||||
} on FirebaseAuthException catch (e) {
|
} on FirebaseAuthException catch (e) {
|
||||||
rethrow;
|
print("FirebaseAuthException code: ${e.code}");
|
||||||
|
print("FirebaseAuthException message: ${e.message}");
|
||||||
|
|
||||||
|
switch (e.code) {
|
||||||
|
case 'invalid-email':
|
||||||
|
throw AuthException("Format email tidak valid.");
|
||||||
|
case 'user-not-found':
|
||||||
|
throw AuthException("Email tidak terdaftar.");
|
||||||
|
case 'wrong-password':
|
||||||
|
throw AuthException("Password salah.");
|
||||||
|
case 'invalid-credential':
|
||||||
|
throw AuthException("Email tidak terdaftar atau password salah.");
|
||||||
|
default:
|
||||||
|
throw AuthException(e.message ?? "Terjadi kesalahan.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
|
||||||
class UserEntity {
|
class UserEntity {
|
||||||
final String uid;
|
final String uid;
|
||||||
final String email;
|
final String email;
|
||||||
|
@ -9,15 +11,15 @@ class UserEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserData {
|
class UserData {
|
||||||
final String name;
|
final String? name;
|
||||||
final String email;
|
final String? email;
|
||||||
final String phone;
|
final String? phone;
|
||||||
final String birthDate;
|
final String? birthDate;
|
||||||
final String gender;
|
final String? gender;
|
||||||
final String work;
|
final String? work;
|
||||||
final String city;
|
final String? city;
|
||||||
final String address;
|
final String? address;
|
||||||
final String role;
|
final String? role;
|
||||||
|
|
||||||
UserData({
|
UserData({
|
||||||
required this.name,
|
required this.name,
|
||||||
|
@ -32,19 +34,30 @@ class UserData {
|
||||||
});
|
});
|
||||||
|
|
||||||
factory UserData.fromMap(Map<String, dynamic> map) {
|
factory UserData.fromMap(Map<String, dynamic> map) {
|
||||||
|
final timestamp = map['birth_date'];
|
||||||
|
String? birthDateString;
|
||||||
|
|
||||||
|
if (timestamp is Timestamp) {
|
||||||
|
// Konversi ke DateTime lalu ke String (format bebas)
|
||||||
|
birthDateString = timestamp.toDate().toIso8601String();
|
||||||
|
} else if (timestamp is String) {
|
||||||
|
// Kalau sudah String, langsung pakai
|
||||||
|
birthDateString = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
return UserData(
|
return UserData(
|
||||||
name: map['name'],
|
name: map['name'] as String?,
|
||||||
email: map['email'],
|
email: map['email'] as String?,
|
||||||
phone: map['phone'],
|
phone: map['phone'] as String?,
|
||||||
birthDate: map['birth_date'],
|
birthDate: birthDateString,
|
||||||
gender: map['gender'],
|
gender: map['gender'] as String?,
|
||||||
work: map['work'],
|
work: map['work'] as String?,
|
||||||
city: map['city'],
|
city: map['city'] as String?,
|
||||||
address: map['address'],
|
address: map['address'] as String?,
|
||||||
role: map['role'],
|
role: map['role'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
Map<String, dynamic> toMap() {
|
||||||
return {
|
return {
|
||||||
'name': name,
|
'name': name,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
import 'package:e_porter/data/repositories/auth_repository_impl.dart';
|
||||||
import 'package:e_porter/domain/usecases/auth_usecase.dart';
|
import 'package:e_porter/domain/usecases/auth_usecase.dart';
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
@ -57,7 +56,7 @@ class AuthController extends GetxController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userData.role.toLowerCase() != effectiveRole.toLowerCase()) {
|
if (userData.role!.toLowerCase() != effectiveRole.toLowerCase()) {
|
||||||
_showErrorSnackbar("Role Tidak Sesuai",
|
_showErrorSnackbar("Role Tidak Sesuai",
|
||||||
"Data user menunjukkan role '${userData.role}', bukan '$effectiveRole'.");
|
"Data user menunjukkan role '${userData.role}', bukan '$effectiveRole'.");
|
||||||
return;
|
return;
|
||||||
|
@ -66,28 +65,8 @@ class AuthController extends GetxController {
|
||||||
await PreferencesService.saveUserData(userData);
|
await PreferencesService.saveUserData(userData);
|
||||||
Get.offAllNamed(Routes.NAVBAR, arguments: effectiveRole);
|
Get.offAllNamed(Routes.NAVBAR, arguments: effectiveRole);
|
||||||
|
|
||||||
} on FirebaseAuthException catch (e) {
|
} on AuthException catch (e) {
|
||||||
print("FirebaseAuthException code: ${e.code}");
|
_showErrorSnackbar("Login Gagal", e.message);
|
||||||
switch (e.code) {
|
|
||||||
case 'user-not-found':
|
|
||||||
_showErrorSnackbar("Login Gagal", "Email belum terdaftar.");
|
|
||||||
break;
|
|
||||||
case 'wrong-password':
|
|
||||||
_showErrorSnackbar("Login Gagal", "Password salah.");
|
|
||||||
break;
|
|
||||||
case 'invalid-email':
|
|
||||||
_showErrorSnackbar("Login Gagal", "Format email tidak valid.");
|
|
||||||
break;
|
|
||||||
case 'invalid-credential':
|
|
||||||
if (e.message != null && e.message!.toLowerCase().contains('password')) {
|
|
||||||
_showErrorSnackbar("Login Gagal", "Password salah.");
|
|
||||||
} else {
|
|
||||||
_showErrorSnackbar("Login Gagal", "Email belum terdaftar.");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
_showErrorSnackbar("Login Gagal", e.message ?? "Terjadi kesalahan.");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_showErrorSnackbar("Terjadi Kesalahan", e.toString());
|
_showErrorSnackbar("Terjadi Kesalahan", e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -5,7 +5,10 @@ import '../../domain/usecases/get_airport.dart';
|
||||||
|
|
||||||
class SearchFlightController extends GetxController {
|
class SearchFlightController extends GetxController {
|
||||||
final GetAirports getAirports;
|
final GetAirports getAirports;
|
||||||
|
|
||||||
var airports = <Airport>[].obs; // Gunakan Rx agar bisa reaktif
|
var airports = <Airport>[].obs; // Gunakan Rx agar bisa reaktif
|
||||||
|
var filteredAirports = <Airport>[].obs;
|
||||||
|
var searchText = ''.obs;
|
||||||
|
|
||||||
SearchFlightController(this.getAirports);
|
SearchFlightController(this.getAirports);
|
||||||
|
|
||||||
|
@ -13,24 +16,27 @@ class SearchFlightController extends GetxController {
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
fetchAirports();
|
fetchAirports();
|
||||||
|
debounce(searchText, (_) => searchAirports(searchText.value), time: Duration(milliseconds: 300));
|
||||||
}
|
}
|
||||||
|
|
||||||
void fetchAirports() async {
|
void fetchAirports() async {
|
||||||
final result = await getAirports();
|
final result = await getAirports();
|
||||||
airports.value = result;
|
airports.value = result;
|
||||||
|
filteredAirports.assignAll(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Misalnya, jika Anda ingin menambahkan pencarian:
|
|
||||||
void searchAirports(String query) {
|
void searchAirports(String query) {
|
||||||
// Contoh filter sederhana
|
final lowerCaseQuery = query.toLowerCase();
|
||||||
final filtered = airports.where((airport) {
|
|
||||||
final city = airport.city.toLowerCase();
|
|
||||||
final code = airport.code.toLowerCase();
|
|
||||||
final name = airport.name.toLowerCase();
|
|
||||||
final q = query.toLowerCase();
|
|
||||||
return city.contains(q) || code.contains(q) || name.contains(q);
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
airports.value = filtered;
|
if (lowerCaseQuery.isEmpty) {
|
||||||
|
filteredAirports.assignAll(airports);
|
||||||
|
} else {
|
||||||
|
filteredAirports.assignAll(airports
|
||||||
|
.where((airport) =>
|
||||||
|
airport.name.toLowerCase().contains(lowerCaseQuery) ||
|
||||||
|
airport.city.toLowerCase().contains(lowerCaseQuery) ||
|
||||||
|
airport.code.toLowerCase().contains(lowerCaseQuery))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ class _BoardingPassScreenState extends State<BoardingPassScreen> with SingleTick
|
||||||
padding: EdgeInsets.only(bottom: 16.h),
|
padding: EdgeInsets.only(bottom: 16.h),
|
||||||
child: CardBoardingPass(
|
child: CardBoardingPass(
|
||||||
isActive: false,
|
isActive: false,
|
||||||
|
onTap: () {},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -78,7 +79,10 @@ class _BoardingPassScreenState extends State<BoardingPassScreen> with SingleTick
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(bottom: 16.h),
|
padding: EdgeInsets.only(bottom: 16.h),
|
||||||
child: CardBoardingPass(isActive: true),
|
child: CardBoardingPass(
|
||||||
|
isActive: true,
|
||||||
|
onTap: () {},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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/component/icons/icons_library.dart';
|
||||||
import 'package:e_porter/_core/constants/colors.dart';
|
import 'package:e_porter/_core/constants/colors.dart';
|
||||||
import 'package:e_porter/_core/constants/typography.dart';
|
import 'package:e_porter/_core/constants/typography.dart';
|
||||||
|
import 'package:e_porter/domain/models/airport.dart';
|
||||||
import 'package:e_porter/presentation/screens/home/component/flight_class_radio.dart';
|
import 'package:e_porter/presentation/screens/home/component/flight_class_radio.dart';
|
||||||
import 'package:e_porter/presentation/screens/home/component/flight_date_selector.dart';
|
import 'package:e_porter/presentation/screens/home/component/flight_date_selector.dart';
|
||||||
import 'package:e_porter/presentation/screens/home/component/flight_selector.dart';
|
import 'package:e_porter/presentation/screens/home/component/flight_selector.dart';
|
||||||
|
@ -25,9 +26,11 @@ class BookingTickets extends StatefulWidget {
|
||||||
class _BookingTicketsState extends State<BookingTickets> {
|
class _BookingTicketsState extends State<BookingTickets> {
|
||||||
DateTime selectedDate = DateTime.now();
|
DateTime selectedDate = DateTime.now();
|
||||||
String selectedDateText = 'dd/mm/yyyy';
|
String selectedDateText = 'dd/mm/yyyy';
|
||||||
|
|
||||||
final ValueNotifier<String> selectedClass = ValueNotifier<String>('Economy');
|
final ValueNotifier<String> selectedClass = ValueNotifier<String>('Economy');
|
||||||
|
|
||||||
|
Airport? selectedAirportFrom;
|
||||||
|
Airport? selectedAirportTo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
@ -51,19 +54,33 @@ class _BookingTicketsState extends State<BookingTickets> {
|
||||||
children: [
|
children: [
|
||||||
FlightSelector(
|
FlightSelector(
|
||||||
label: 'Dari',
|
label: 'Dari',
|
||||||
hintText: 'Pilih Bandara',
|
hintText: selectedAirportFrom == null
|
||||||
|
? 'Pilih Bandara'
|
||||||
|
: '${selectedAirportFrom!.code} - ${selectedAirportFrom!.city}',
|
||||||
svgIconPath: CustomeIcons.PlaneRightOutline(),
|
svgIconPath: CustomeIcons.PlaneRightOutline(),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Get.toNamed(Routes.SEARCHFLIGHT);
|
final result = await Get.toNamed(Routes.SEARCHFLIGHT);
|
||||||
|
if (result != null) {
|
||||||
|
setState(() {
|
||||||
|
selectedAirportFrom = result;
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
FlightSelector(
|
FlightSelector(
|
||||||
label: 'Ke',
|
label: 'Ke',
|
||||||
hintText: 'Pilih Bandara',
|
hintText: selectedAirportTo == null
|
||||||
|
? 'Pilih Bandara'
|
||||||
|
: '${selectedAirportTo!.code} - ${selectedAirportTo!.city}',
|
||||||
svgIconPath: CustomeIcons.PlaneLeftOutline(),
|
svgIconPath: CustomeIcons.PlaneLeftOutline(),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
Get.toNamed(Routes.SEARCHFLIGHT);
|
final result = await Get.toNamed(Routes.SEARCHFLIGHT);
|
||||||
|
if (result != null) {
|
||||||
|
setState(() {
|
||||||
|
selectedAirportTo = result;
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(height: 16.h),
|
SizedBox(height: 16.h),
|
||||||
|
|
|
@ -250,7 +250,28 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(height: 32.w),
|
SizedBox(height: 32.w),
|
||||||
CustomeShadowCotainner(child: TypographyStyles.body('Mulai Antrian'))
|
CustomeShadowCotainner(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 6.h),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: PrimaryColors.primary200,
|
||||||
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
|
),
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/icons/ic_account.svg',
|
||||||
|
width: 32.w,
|
||||||
|
height: 32.h,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 10.h),
|
||||||
|
TypographyStyles.body(
|
||||||
|
'Mulai Antrian',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -34,7 +34,12 @@ class _SearchFlightScreenState extends State<SearchFlightScreen> {
|
||||||
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SearchBarComponent(hintText: 'Cari Bandara'),
|
SearchBarComponent(
|
||||||
|
hintText: 'Cari Bandara',
|
||||||
|
onChanged: (value) {
|
||||||
|
controller.searchText.value = value;
|
||||||
|
},
|
||||||
|
),
|
||||||
SizedBox(height: 20.h),
|
SizedBox(height: 20.h),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -45,7 +50,11 @@ class _SearchFlightScreenState extends State<SearchFlightScreen> {
|
||||||
borderRadius: BorderRadius.circular(10.r),
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
),
|
),
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
final airports = controller.airports;
|
final airports = controller.filteredAirports;
|
||||||
|
|
||||||
|
if (airports.isEmpty) {
|
||||||
|
return _buildIsEmpty();
|
||||||
|
}
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: airports.length,
|
itemCount: airports.length,
|
||||||
|
@ -54,7 +63,9 @@ class _SearchFlightScreenState extends State<SearchFlightScreen> {
|
||||||
return _buildCardItem(
|
return _buildCardItem(
|
||||||
'${airport.code} - ${airport.city}',
|
'${airport.code} - ${airport.city}',
|
||||||
'${airport.name}',
|
'${airport.name}',
|
||||||
() {},
|
() {
|
||||||
|
Get.back(result: airport);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -100,4 +111,16 @@ class _SearchFlightScreenState extends State<SearchFlightScreen> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildIsEmpty() {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 16.w),
|
||||||
|
width: double.infinity,
|
||||||
|
child: TypographyStyles.caption(
|
||||||
|
"Data tidak ditemukan",
|
||||||
|
color: GrayColors.gray400,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue