Feat: done logic search bar

This commit is contained in:
orangdeso 2025-03-08 16:03:48 +07:00
parent bd52aed744
commit 381f4f6a8f
22 changed files with 159 additions and 73 deletions

View File

@ -434,7 +434,7 @@
"languageVersion": "3.4"
}
],
"generated": "2025-03-05T07:39:52.656986Z",
"generated": "2025-03-07T08:39:17.979426Z",
"generator": "pub",
"generatorVersion": "3.5.0",
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",

File diff suppressed because one or more lines are too long

View File

@ -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\:/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

View File

@ -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.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

View File

@ -5,10 +5,12 @@ import 'package:flutter_svg/flutter_svg.dart';
class SearchBarComponent extends StatelessWidget {
final String hintText;
final ValueChanged<String>? onChanged;
const SearchBarComponent({
Key? key,
required this.hintText,
this.onChanged
});
@override
@ -18,6 +20,7 @@ class SearchBarComponent extends StatelessWidget {
color: Colors.white,
),
child: TextField(
onChanged: onChanged,
decoration: InputDecoration(
prefixIcon: Padding(
padding: EdgeInsets.only(left: 16.w, right: 11.w),

View File

@ -26,6 +26,7 @@ class PreferencesService {
final now = DateTime.now().millisecondsSinceEpoch;
if (now > expiredAt) {
await clearUserData();
print("now: $now, expiredAt: $expiredAt");
return null;
}
@ -36,7 +37,7 @@ class PreferencesService {
final userData = UserData.fromMap(map);
return userData;
}
static Future<void> clearUserData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_userDataKey);

View File

@ -3,6 +3,11 @@ import 'package:e_porter/domain/models/user_entity.dart';
import 'package:e_porter/domain/repositories/auth_repository.dart';
import 'package:firebase_auth/firebase_auth.dart';
class AuthException implements Exception {
final String message;
AuthException(this.message);
}
class AuthRepositoryImpl implements AuthRepository {
final FirebaseAuth _firebaseAuth;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
@ -19,7 +24,21 @@ class AuthRepositoryImpl implements AuthRepository {
final user = userCredential.user!;
return UserEntity(uid: user.uid, email: user.email ?? "");
} 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.");
}
}
}

View File

@ -1,3 +1,5 @@
import 'package:cloud_firestore/cloud_firestore.dart';
class UserEntity {
final String uid;
final String email;
@ -9,15 +11,15 @@ class UserEntity {
}
class UserData {
final String name;
final String email;
final String phone;
final String birthDate;
final String gender;
final String work;
final String city;
final String address;
final String role;
final String? name;
final String? email;
final String? phone;
final String? birthDate;
final String? gender;
final String? work;
final String? city;
final String? address;
final String? role;
UserData({
required this.name,
@ -32,19 +34,30 @@ class UserData {
});
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(
name: map['name'],
email: map['email'],
phone: map['phone'],
birthDate: map['birth_date'],
gender: map['gender'],
work: map['work'],
city: map['city'],
address: map['address'],
role: map['role'],
name: map['name'] as String?,
email: map['email'] as String?,
phone: map['phone'] as String?,
birthDate: birthDateString,
gender: map['gender'] as String?,
work: map['work'] as String?,
city: map['city'] as String?,
address: map['address'] as String?,
role: map['role'] as String?,
);
}
Map<String, dynamic> toMap() {
return {
'name': name,

View File

@ -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:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@ -57,7 +56,7 @@ class AuthController extends GetxController {
return;
}
if (userData.role.toLowerCase() != effectiveRole.toLowerCase()) {
if (userData.role!.toLowerCase() != effectiveRole.toLowerCase()) {
_showErrorSnackbar("Role Tidak Sesuai",
"Data user menunjukkan role '${userData.role}', bukan '$effectiveRole'.");
return;
@ -66,28 +65,8 @@ class AuthController extends GetxController {
await PreferencesService.saveUserData(userData);
Get.offAllNamed(Routes.NAVBAR, arguments: effectiveRole);
} on FirebaseAuthException catch (e) {
print("FirebaseAuthException code: ${e.code}");
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.");
}
} on AuthException catch (e) {
_showErrorSnackbar("Login Gagal", e.message);
} catch (e) {
_showErrorSnackbar("Terjadi Kesalahan", e.toString());
} finally {

View File

@ -5,7 +5,10 @@ import '../../domain/usecases/get_airport.dart';
class SearchFlightController extends GetxController {
final GetAirports getAirports;
var airports = <Airport>[].obs; // Gunakan Rx agar bisa reaktif
var filteredAirports = <Airport>[].obs;
var searchText = ''.obs;
SearchFlightController(this.getAirports);
@ -13,24 +16,27 @@ class SearchFlightController extends GetxController {
void onInit() {
super.onInit();
fetchAirports();
debounce(searchText, (_) => searchAirports(searchText.value), time: Duration(milliseconds: 300));
}
void fetchAirports() async {
final result = await getAirports();
airports.value = result;
filteredAirports.assignAll(result);
}
// Misalnya, jika Anda ingin menambahkan pencarian:
void searchAirports(String query) {
// Contoh filter sederhana
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();
final lowerCaseQuery = query.toLowerCase();
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());
}
}
}
}

View File

@ -69,6 +69,7 @@ class _BoardingPassScreenState extends State<BoardingPassScreen> with SingleTick
padding: EdgeInsets.only(bottom: 16.h),
child: CardBoardingPass(
isActive: false,
onTap: () {},
),
);
},
@ -78,7 +79,10 @@ class _BoardingPassScreenState extends State<BoardingPassScreen> with SingleTick
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: CardBoardingPass(isActive: true),
child: CardBoardingPass(
isActive: true,
onTap: () {},
),
);
},
),

View File

@ -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/constants/colors.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_date_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> {
DateTime selectedDate = DateTime.now();
String selectedDateText = 'dd/mm/yyyy';
final ValueNotifier<String> selectedClass = ValueNotifier<String>('Economy');
Airport? selectedAirportFrom;
Airport? selectedAirportTo;
@override
Widget build(BuildContext context) {
return Scaffold(
@ -51,19 +54,33 @@ class _BookingTicketsState extends State<BookingTickets> {
children: [
FlightSelector(
label: 'Dari',
hintText: 'Pilih Bandara',
hintText: selectedAirportFrom == null
? 'Pilih Bandara'
: '${selectedAirportFrom!.code} - ${selectedAirportFrom!.city}',
svgIconPath: CustomeIcons.PlaneRightOutline(),
onTap: () {
Get.toNamed(Routes.SEARCHFLIGHT);
onTap: () async {
final result = await Get.toNamed(Routes.SEARCHFLIGHT);
if (result != null) {
setState(() {
selectedAirportFrom = result;
});
}
},
),
SizedBox(height: 16.h),
FlightSelector(
label: 'Ke',
hintText: 'Pilih Bandara',
hintText: selectedAirportTo == null
? 'Pilih Bandara'
: '${selectedAirportTo!.code} - ${selectedAirportTo!.city}',
svgIconPath: CustomeIcons.PlaneLeftOutline(),
onTap: () {
Get.toNamed(Routes.SEARCHFLIGHT);
onTap: () async {
final result = await Get.toNamed(Routes.SEARCHFLIGHT);
if (result != null) {
setState(() {
selectedAirportTo = result;
});
}
},
),
SizedBox(height: 16.h),

View File

@ -250,7 +250,28 @@ class _HomeScreenState extends State<HomeScreen> {
],
),
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',
),
],
),
)
],
),
),

View File

@ -34,7 +34,12 @@ class _SearchFlightScreenState extends State<SearchFlightScreen> {
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
child: Column(
children: [
SearchBarComponent(hintText: 'Cari Bandara'),
SearchBarComponent(
hintText: 'Cari Bandara',
onChanged: (value) {
controller.searchText.value = value;
},
),
SizedBox(height: 20.h),
Expanded(
child: Container(
@ -45,7 +50,11 @@ class _SearchFlightScreenState extends State<SearchFlightScreen> {
borderRadius: BorderRadius.circular(10.r),
),
child: Obx(() {
final airports = controller.airports;
final airports = controller.filteredAirports;
if (airports.isEmpty) {
return _buildIsEmpty();
}
return ListView.builder(
shrinkWrap: true,
itemCount: airports.length,
@ -54,7 +63,9 @@ class _SearchFlightScreenState extends State<SearchFlightScreen> {
return _buildCardItem(
'${airport.code} - ${airport.city}',
'${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,
),
);
}
}