Feat: fix change data complete screen

This commit is contained in:
orangdeso 2025-05-29 00:13:12 +07:00
parent 59817eb99d
commit ae09c5a89a
8 changed files with 288 additions and 155 deletions

View File

@ -163,6 +163,29 @@ class ProfilRepositoryImpl implements ProfilRepository {
}); });
} }
@override
Future<void> updateUserData(UserData userData) async {
try {
if (userData.uid.isEmpty) {
throw Exception("User ID tidak boleh kosong");
}
await _firestore.collection('users').doc(userData.uid).update({
'name': userData.name,
'birth_date': userData.birthDate,
'gender': userData.gender,
'work': userData.work,
'city': userData.city,
'address': userData.address
});
log("Data pengguna berhasil diperbarui untuk ID: ${userData.uid}");
} catch (e) {
log("Error saat memperbarui data pengguna: $e");
rethrow;
}
}
// @override // @override
// Future<void> changeEmail({ // Future<void> changeEmail({
// required String oldPassword, // required String oldPassword,

View File

@ -18,6 +18,7 @@ class ProfilBinding extends Bindings {
Get.lazyPut(() => ChangeNoIdUseCase(Get.find())); Get.lazyPut(() => ChangeNoIdUseCase(Get.find()));
Get.lazyPut(() => DeletePassengerUseCase(Get.find())); Get.lazyPut(() => DeletePassengerUseCase(Get.find()));
Get.lazyPut(() => UpdatePassengerUseCase(Get.find())); Get.lazyPut(() => UpdatePassengerUseCase(Get.find()));
Get.lazyPut(() => UpdateUserDataUseCase(Get.find()));
// Get.lazyPut(() => ChangeEmailUseCase(Get.find())); // Get.lazyPut(() => ChangeEmailUseCase(Get.find()));
Get.lazyPut( Get.lazyPut(
@ -30,6 +31,7 @@ class ProfilBinding extends Bindings {
changeNoIdUseCase: Get.find(), changeNoIdUseCase: Get.find(),
deletePassengerUseCase: Get.find(), deletePassengerUseCase: Get.find(),
updatePassengerUseCase: Get.find(), updatePassengerUseCase: Get.find(),
updateUserDataUseCase: Get.find(),
), ),
); );
} }

View File

@ -37,6 +37,8 @@ abstract class ProfilRepository {
required String noId, required String noId,
}); });
Future<void> updateUserData(UserData userData);
// Future<void> changeEmail({ // Future<void> changeEmail({
// required String oldPassword, // required String oldPassword,
// required String newEmail, // required String newEmail,

View File

@ -127,3 +127,12 @@ class UpdatePassengerUseCase {
// ); // );
// } // }
// } // }
class UpdateUserDataUseCase {
final ProfilRepository profilRepository;
UpdateUserDataUseCase(this.profilRepository);
Future<void> call(UserData userData) {
return profilRepository.updateUserData(userData);
}
}

View File

@ -15,6 +15,7 @@ class ProfilController extends GetxController {
final ChangeNoIdUseCase changeNoIdUseCase; final ChangeNoIdUseCase changeNoIdUseCase;
final DeletePassengerUseCase deletePassengerUseCase; final DeletePassengerUseCase deletePassengerUseCase;
final UpdatePassengerUseCase updatePassengerUseCase; final UpdatePassengerUseCase updatePassengerUseCase;
final UpdateUserDataUseCase updateUserDataUseCase;
// final ChangeEmailUseCase changeEmailUseCase; // final ChangeEmailUseCase changeEmailUseCase;
var passengerList = <PassengerModel>[].obs; var passengerList = <PassengerModel>[].obs;
@ -25,6 +26,7 @@ class ProfilController extends GetxController {
var isChangingNoId = false.obs; var isChangingNoId = false.obs;
var isDeletingPassenger = false.obs; var isDeletingPassenger = false.obs;
var isUpdatingPassenger = false.obs; var isUpdatingPassenger = false.obs;
var isUpdatingUserData = false.obs;
// var isChangingEmail = false.obs; // var isChangingEmail = false.obs;
ProfilController({ ProfilController({
@ -36,6 +38,7 @@ class ProfilController extends GetxController {
required this.changeNoIdUseCase, required this.changeNoIdUseCase,
required this.deletePassengerUseCase, required this.deletePassengerUseCase,
required this.updatePassengerUseCase, required this.updatePassengerUseCase,
required this.updateUserDataUseCase,
// required this.changeEmailUseCase, // required this.changeEmailUseCase,
}); });
@ -244,6 +247,47 @@ class ProfilController extends GetxController {
} }
} }
Future<bool> updateUserData({
required String name,
String? birthDate,
String? gender,
String? work,
String? city,
String? address,
}) async {
isUpdatingUserData.value = true;
try {
final currentUser = userData.value;
if (currentUser == null) {
throw Exception("Data pengguna tidak tersedia");
}
final updatedUserData = UserData(
uid: currentUser.uid,
email: currentUser.email,
name: name,
birthDate: birthDate,
gender: gender,
work: work,
city: city,
address: address,
phone: currentUser.phone,
role: currentUser.role,
noId: currentUser.noId,
tipeId: currentUser.tipeId,
);
await updateUserDataUseCase(updatedUserData);
await _loadProfile();
SnackbarHelper.showSuccess("Berhasil", "Anda telah berhasil memperbarui data diri");
return true;
} catch (e) {
SnackbarHelper.showError("Gagal", "Terjadi kesalahan: $e");
return false;
} finally {
isUpdatingUserData.value = false;
}
}
// Future<bool> changeEmail({ // Future<bool> changeEmail({
// required String oldPassword, // required String oldPassword,
// required String newEmail, // required String newEmail,
@ -279,40 +323,4 @@ class ProfilController extends GetxController {
// isChangingEmail.value = false; // isChangingEmail.value = false;
// } // }
// } // }
// Future<bool> changeEmail({
// required String oldPassword,
// required String newEmail,
// }) async {
// isChangingEmail.value = true;
// try {
// await changeEmailUseCase(oldPassword: oldPassword, newEmail: newEmail);
// SnackbarHelper.showSuccess("Link Terkirim", "Silakan cek email $newEmail untuk verifikasi.");
// return true;
// } on FirebaseAuthException catch (e) {
// switch (e.code) {
// case 'wrong-password':
// case 'invalid-credential':
// SnackbarHelper.showError("Gagal", "Password lama salah.");
// break;
// case 'email-already-in-use':
// SnackbarHelper.showError("Gagal", "Email baru sudah digunakan.");
// break;
// case 'unauthorized-domain':
// SnackbarHelper.showError(
// "Gagal",
// "Domain verifikasi belum diizinkan. "
// "Tambahkan \"eporter.page.link\" di Firebase Console → Authentication → Settings → Authorized domains.");
// break;
// default:
// SnackbarHelper.showError("Gagal", e.message ?? e.code);
// }
// return false;
// } catch (_) {
// SnackbarHelper.showError("Gagal", "Terjadi kesalahan.");
// return false;
// } finally {
// isChangingEmail.value = false;
// }
// }
} }

View File

@ -59,15 +59,15 @@ class _InputFormState extends State<InputForm> {
widget.inputFormatters ?? <TextInputFormatter>[FilteringTextInputFormatter.singleLineFormatter], widget.inputFormatters ?? <TextInputFormatter>[FilteringTextInputFormatter.singleLineFormatter],
style: TextStyle( style: TextStyle(
fontFamily: 'DMSans', fontFamily: 'DMSans',
fontSize: 14.sp, fontSize: 16.sp,
fontWeight: (!widget.enabled && hasValidValue) ? FontWeight.w500 : FontWeight.w400, fontWeight: (widget.enabled && hasValidValue) ? FontWeight.w500 : FontWeight.w500,
color: hasValidValue ? GrayColors.gray800 : GrayColors.gray500, color: hasValidValue ? GrayColors.gray800 : GrayColors.gray800,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: widget.hintText, hintText: widget.hintText,
hintStyle: TextStyle( hintStyle: TextStyle(
fontFamily: 'DMSans', fontFamily: 'DMSans',
fontSize: 14.sp, fontSize: 16.sp,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: GrayColors.gray500, color: GrayColors.gray500,
), ),

View File

@ -1,5 +1,4 @@
import 'dart:developer'; import 'dart:developer';
import 'package:e_porter/_core/component/appbar/appbar_component.dart'; import 'package:e_porter/_core/component/appbar/appbar_component.dart';
import 'package:e_porter/_core/component/button/button_fill.dart'; import 'package:e_porter/_core/component/button/button_fill.dart';
import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart'; import 'package:e_porter/_core/component/card/custome_shadow_cotainner.dart';
@ -7,10 +6,14 @@ import 'package:e_porter/_core/component/text/custom_text.dart';
import 'package:e_porter/_core/component/text_field/dropdown/dropdown_component.dart'; import 'package:e_porter/_core/component/text_field/dropdown/dropdown_component.dart';
import 'package:e_porter/_core/constants/colors.dart'; import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/utils/formatter/uppercase_helper.dart'; import 'package:e_porter/_core/utils/formatter/uppercase_helper.dart';
import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart';
import 'package:e_porter/_core/validators/validators.dart'; import 'package:e_porter/_core/validators/validators.dart';
import 'package:e_porter/domain/models/user_entity.dart';
import 'package:e_porter/presentation/controllers/profil_controller.dart';
import 'package:e_porter/presentation/screens/auth/component/Input_form.dart'; import 'package:e_porter/presentation/screens/auth/component/Input_form.dart';
import 'package:e_porter/presentation/screens/profile/component/header_information.dart'; import 'package:e_porter/presentation/screens/profile/component/header_information.dart';
import 'package:e_porter/presentation/screens/profile/component/radio_button.dart'; import 'package:e_porter/presentation/screens/profile/component/radio_button.dart';
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -27,10 +30,13 @@ class ChangeDataComplete extends StatefulWidget {
class _ChangeDataCompleteState extends State<ChangeDataComplete> { class _ChangeDataCompleteState extends State<ChangeDataComplete> {
DateTime selectedDate = DateTime.now(); DateTime selectedDate = DateTime.now();
String selectedDateText = 'dd/mm/yyyy'; String selectedDateText = 'dd/mm/yyyy';
String? selectedTypeId; String? selectedWork;
final _profileController = Get.find<ProfilController>();
final _nameController = TextEditingController(); final _nameController = TextEditingController();
final _dateController = TextEditingController(); final _dateController = TextEditingController();
final _cityController = TextEditingController();
final _addressController = TextEditingController();
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final ValueNotifier<String> selectedGender = ValueNotifier<String>('Laki-laki'); final ValueNotifier<String> selectedGender = ValueNotifier<String>('Laki-laki');
@ -38,15 +44,84 @@ class _ChangeDataCompleteState extends State<ChangeDataComplete> {
void initState() { void initState() {
super.initState(); super.initState();
_dateController.text = 'dd/mm/yyyy'; _dateController.text = 'dd/mm/yyyy';
_initializeData();
} }
@override @override
void dispose() { void dispose() {
_nameController.dispose(); _nameController.dispose();
_dateController.dispose(); _dateController.dispose();
_cityController.dispose();
_addressController.dispose();
selectedGender.dispose();
super.dispose(); super.dispose();
} }
void _initializeData() {
final userData = _profileController.userData.value;
if (userData != null) {
_nameController.text = userData.name ?? '';
if (userData.birthDate != null && userData.birthDate!.isNotEmpty) {
try {
selectedDate = DateFormat('dd MMMM yyyy', 'en_US').parse(userData.birthDate!);
_dateController.text = userData.birthDate!;
} catch (e) {
_dateController.text = 'dd/mm/yyyy';
}
} else {
_dateController.text = 'dd/mm/yyyy';
}
selectedGender.value = userData.gender ?? 'Laki-laki';
selectedWork = userData.work;
_cityController.text = userData.city ?? '';
_addressController.text = userData.address ?? '';
}
}
Future<void> _handleSaveData() async {
if (!_formKey.currentState!.validate()) {
return;
}
final userData = _profileController.userData.value;
String formattedBirthDate = DateFormat('dd MMMM yyyy', 'en_US').format(selectedDate);
bool hasChanges = _hasDataChanged(userData, formattedBirthDate);
if (!hasChanges) {
SnackbarHelper.showInfo('Info', 'Tidak ada perubahan data yang perlu disimpan');
return;
}
final success = await _profileController.updateUserData(
name: _nameController.text.trim(),
birthDate: _dateController.text,
gender: selectedGender.value,
work: selectedWork!,
city: _cityController.text.trim(),
address: _addressController.text.trim(),
);
if (success) {
Get.toNamed(Routes.INFORMATIONS);
}
}
bool _hasDataChanged(UserData? userData, String formattedBirthDate) {
if (userData == null) return true;
bool nameChanged = (_nameController.text.trim()) != (userData.name ?? '');
bool birthDateChanged = formattedBirthDate != (userData.birthDate ?? '');
bool genderChanged = selectedGender.value != (userData.gender ?? 'Laki-laki');
bool workChanged = selectedWork != userData.work;
bool cityChanged = (_cityController.text.trim()) != (userData.city ?? '');
bool addressChanged = (_addressController.text.trim()) != (userData.address ?? '');
return nameChanged || birthDateChanged || genderChanged || workChanged || cityChanged || addressChanged;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -68,11 +143,14 @@ class _ChangeDataCompleteState extends State<ChangeDataComplete> {
title: 'Pastikan anda memasukkan informasi mengenai data diri anda dengan benar!', title: 'Pastikan anda memasukkan informasi mengenai data diri anda dengan benar!',
), ),
), ),
CustomeShadowCotainner( Expanded(
child: CustomeShadowCotainner(
height: MediaQuery.of(context).size.height * 0.67, height: MediaQuery.of(context).size.height * 0.67,
borderRadius: BorderRadius.circular(0), borderRadius: BorderRadius.circular(0),
child: SingleChildScrollView( child: SingleChildScrollView(
padding: EdgeInsets.only(bottom: 10.h), padding: EdgeInsets.only(bottom: 10.h),
child: Form(
key: _formKey,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -148,10 +226,10 @@ class _ChangeDataCompleteState extends State<ChangeDataComplete> {
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
hintText: "Pilih pekerjaan", hintText: "Pilih pekerjaan",
items: ['PELAJAR/MAHASISWA', 'KARYAWAN SWASTA', 'WIRASWASTA', 'PNS', 'TNI/POLRI'], items: ['PELAJAR/MAHASISWA', 'KARYAWAN SWASTA', 'WIRASWASTA', 'PNS', 'TNI/POLRI'],
value: selectedTypeId, value: selectedWork,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
selectedTypeId = value!; selectedWork = value;
}); });
}, },
), ),
@ -159,6 +237,7 @@ class _ChangeDataCompleteState extends State<ChangeDataComplete> {
CustomText.textPadding8('Kota / Kabupaten'), CustomText.textPadding8('Kota / Kabupaten'),
SizedBox(height: 16.h), SizedBox(height: 16.h),
InputForm( InputForm(
controller: _cityController,
hintText: 'Kota / Kabupaten', hintText: 'Kota / Kabupaten',
svgIconPath: 'assets/icons/ic_account.svg', svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white, backgroundColor: Colors.white,
@ -169,6 +248,7 @@ class _ChangeDataCompleteState extends State<ChangeDataComplete> {
CustomText.textPadding8('Alamat'), CustomText.textPadding8('Alamat'),
SizedBox(height: 16.h), SizedBox(height: 16.h),
InputForm( InputForm(
controller: _addressController,
hintText: 'Jl. Contoh Alamat No. 123', hintText: 'Jl. Contoh Alamat No. 123',
svgIconPath: 'assets/icons/ic_account.svg', svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white, backgroundColor: Colors.white,
@ -179,15 +259,24 @@ class _ChangeDataCompleteState extends State<ChangeDataComplete> {
), ),
), ),
), ),
),
),
], ],
), ),
), ),
bottomNavigationBar: CustomeShadowCotainner( bottomNavigationBar: Obx(
() => CustomeShadowCotainner(
borderRadius: BorderRadius.circular(0), borderRadius: BorderRadius.circular(0),
child: ButtonFill( child: ButtonFill(
text: 'Simpan', text: _profileController.isUpdatingUserData.value ? 'Menyimpan...' : 'Simpan',
textColor: Colors.white, textColor: Colors.white,
onTap: () {}, isLoading: _profileController.isUpdatingUserData.value,
onTap: _profileController.isUpdatingUserData.value
? null
: () async {
await _handleSaveData();
},
),
), ),
), ),
); );

View File

@ -113,7 +113,7 @@ class _InformationUsersScreenState extends State<InformationUsersScreen> {
label: 'Nama Lengkap', label: 'Nama Lengkap',
value: userData.name ?? '-', value: userData.name ?? '-',
isDivider: false, isDivider: false,
onTap: () { onTap: () async {
Get.toNamed(Routes.CHANGEDATACOMPLETE); Get.toNamed(Routes.CHANGEDATACOMPLETE);
}, },
), ),