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
// Future<void> changeEmail({
// required String oldPassword,

View File

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

View File

@ -25,7 +25,7 @@ abstract class ProfilRepository {
required String oldPassword,
required String newPassword,
});
Future<void> changePhone({
required String oldPassword,
required String newPhone,
@ -37,6 +37,8 @@ abstract class ProfilRepository {
required String noId,
});
Future<void> updateUserData(UserData userData);
// Future<void> changeEmail({
// required String oldPassword,
// 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 DeletePassengerUseCase deletePassengerUseCase;
final UpdatePassengerUseCase updatePassengerUseCase;
final UpdateUserDataUseCase updateUserDataUseCase;
// final ChangeEmailUseCase changeEmailUseCase;
var passengerList = <PassengerModel>[].obs;
@ -25,6 +26,7 @@ class ProfilController extends GetxController {
var isChangingNoId = false.obs;
var isDeletingPassenger = false.obs;
var isUpdatingPassenger = false.obs;
var isUpdatingUserData = false.obs;
// var isChangingEmail = false.obs;
ProfilController({
@ -36,6 +38,7 @@ class ProfilController extends GetxController {
required this.changeNoIdUseCase,
required this.deletePassengerUseCase,
required this.updatePassengerUseCase,
required this.updateUserDataUseCase,
// 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({
// required String oldPassword,
// required String newEmail,
@ -279,40 +323,4 @@ class ProfilController extends GetxController {
// 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],
style: TextStyle(
fontFamily: 'DMSans',
fontSize: 14.sp,
fontWeight: (!widget.enabled && hasValidValue) ? FontWeight.w500 : FontWeight.w400,
color: hasValidValue ? GrayColors.gray800 : GrayColors.gray500,
fontSize: 16.sp,
fontWeight: (widget.enabled && hasValidValue) ? FontWeight.w500 : FontWeight.w500,
color: hasValidValue ? GrayColors.gray800 : GrayColors.gray800,
),
decoration: InputDecoration(
hintText: widget.hintText,
hintStyle: TextStyle(
fontFamily: 'DMSans',
fontSize: 14.sp,
fontSize: 16.sp,
fontWeight: FontWeight.w400,
color: GrayColors.gray500,
),

View File

@ -1,5 +1,4 @@
import 'dart:developer';
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/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/constants/colors.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/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/profile/component/header_information.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/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -27,10 +30,13 @@ class ChangeDataComplete extends StatefulWidget {
class _ChangeDataCompleteState extends State<ChangeDataComplete> {
DateTime selectedDate = DateTime.now();
String selectedDateText = 'dd/mm/yyyy';
String? selectedTypeId;
String? selectedWork;
final _profileController = Get.find<ProfilController>();
final _nameController = TextEditingController();
final _dateController = TextEditingController();
final _cityController = TextEditingController();
final _addressController = TextEditingController();
final _formKey = GlobalKey<FormState>();
final ValueNotifier<String> selectedGender = ValueNotifier<String>('Laki-laki');
@ -38,15 +44,84 @@ class _ChangeDataCompleteState extends State<ChangeDataComplete> {
void initState() {
super.initState();
_dateController.text = 'dd/mm/yyyy';
_initializeData();
}
@override
void dispose() {
_nameController.dispose();
_dateController.dispose();
_cityController.dispose();
_addressController.dispose();
selectedGender.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
Widget build(BuildContext context) {
return Scaffold(
@ -68,126 +143,140 @@ class _ChangeDataCompleteState extends State<ChangeDataComplete> {
title: 'Pastikan anda memasukkan informasi mengenai data diri anda dengan benar!',
),
),
CustomeShadowCotainner(
height: MediaQuery.of(context).size.height * 0.67,
borderRadius: BorderRadius.circular(0),
child: SingleChildScrollView(
padding: EdgeInsets.only(bottom: 10.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomText.textPadding8('Nama Lengkap'),
SizedBox(height: 16.h),
InputForm(
controller: _nameController,
hintText: 'SUPARJO',
svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white,
validator: Validators.validatorName,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\s]')),
UpperCaseTextFormatter(),
],
textInputType: TextInputType.text,
),
SizedBox(height: 20.h),
CustomText.textPadding8('Tanggal Lahir'),
SizedBox(height: 16.h),
InputForm(
controller: _dateController,
hintText: 'dd/mm/yyyy',
svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white,
enabled: false,
validator: Validators.validatorName,
textInputType: TextInputType.text,
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1950),
lastDate: DateTime.now(),
builder: (context, child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: PrimaryColors.primary800,
onPrimary: Colors.white,
surface: Colors.white,
),
dialogBackgroundColor: Colors.white,
),
child: child!,
);
},
);
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
_dateController.text = DateFormat('dd MMMM yyyy', 'en_US').format(selectedDate);
log(selectedDate.toString());
});
}
},
),
SizedBox(height: 20.h),
CustomText.textPadding8('Jenis Kelamin'),
SizedBox(height: 16.h),
Row(
Expanded(
child: CustomeShadowCotainner(
height: MediaQuery.of(context).size.height * 0.67,
borderRadius: BorderRadius.circular(0),
child: SingleChildScrollView(
padding: EdgeInsets.only(bottom: 10.h),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RadioButtonGender(value: 'Laki-laki', label: 'Laki-laki', selectedGender: selectedGender),
SizedBox(width: 40.h),
RadioButtonGender(value: 'Perempuan', label: 'Perempuan', selectedGender: selectedGender),
CustomText.textPadding8('Nama Lengkap'),
SizedBox(height: 16.h),
InputForm(
controller: _nameController,
hintText: 'SUPARJO',
svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white,
validator: Validators.validatorName,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\s]')),
UpperCaseTextFormatter(),
],
textInputType: TextInputType.text,
),
SizedBox(height: 20.h),
CustomText.textPadding8('Tanggal Lahir'),
SizedBox(height: 16.h),
InputForm(
controller: _dateController,
hintText: 'dd/mm/yyyy',
svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white,
enabled: false,
validator: Validators.validatorName,
textInputType: TextInputType.text,
onTap: () async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1950),
lastDate: DateTime.now(),
builder: (context, child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: PrimaryColors.primary800,
onPrimary: Colors.white,
surface: Colors.white,
),
dialogBackgroundColor: Colors.white,
),
child: child!,
);
},
);
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
_dateController.text = DateFormat('dd MMMM yyyy', 'en_US').format(selectedDate);
log(selectedDate.toString());
});
}
},
),
SizedBox(height: 20.h),
CustomText.textPadding8('Jenis Kelamin'),
SizedBox(height: 16.h),
Row(
children: [
RadioButtonGender(value: 'Laki-laki', label: 'Laki-laki', selectedGender: selectedGender),
SizedBox(width: 40.h),
RadioButtonGender(value: 'Perempuan', label: 'Perempuan', selectedGender: selectedGender),
],
),
SizedBox(height: 20.h),
CustomText.textPadding8('Pekerjaan'),
SizedBox(height: 16.h),
DropdownComponent(
width: MediaQuery.of(context).size.width,
hintText: "Pilih pekerjaan",
items: ['PELAJAR/MAHASISWA', 'KARYAWAN SWASTA', 'WIRASWASTA', 'PNS', 'TNI/POLRI'],
value: selectedWork,
onChanged: (value) {
setState(() {
selectedWork = value;
});
},
),
SizedBox(height: 20.h),
CustomText.textPadding8('Kota / Kabupaten'),
SizedBox(height: 16.h),
InputForm(
controller: _cityController,
hintText: 'Kota / Kabupaten',
svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white,
validator: Validators.validatorName,
textInputType: TextInputType.text,
),
SizedBox(height: 20.h),
CustomText.textPadding8('Alamat'),
SizedBox(height: 16.h),
InputForm(
controller: _addressController,
hintText: 'Jl. Contoh Alamat No. 123',
svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white,
validator: Validators.validatorName,
textInputType: TextInputType.text,
),
],
),
SizedBox(height: 20.h),
CustomText.textPadding8('Pekerjaan'),
SizedBox(height: 16.h),
DropdownComponent(
width: MediaQuery.of(context).size.width,
hintText: "Pilih pekerjaan",
items: ['PELAJAR/MAHASISWA', 'KARYAWAN SWASTA', 'WIRASWASTA', 'PNS', 'TNI/POLRI'],
value: selectedTypeId,
onChanged: (value) {
setState(() {
selectedTypeId = value!;
});
},
),
SizedBox(height: 20.h),
CustomText.textPadding8('Kota / Kabupaten'),
SizedBox(height: 16.h),
InputForm(
hintText: 'Kota / Kabupaten',
svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white,
validator: Validators.validatorName,
textInputType: TextInputType.text,
),
SizedBox(height: 20.h),
CustomText.textPadding8('Alamat'),
SizedBox(height: 16.h),
InputForm(
hintText: 'Jl. Contoh Alamat No. 123',
svgIconPath: 'assets/icons/ic_account.svg',
backgroundColor: Colors.white,
validator: Validators.validatorName,
textInputType: TextInputType.text,
),
],
),
),
),
),
],
),
),
bottomNavigationBar: CustomeShadowCotainner(
borderRadius: BorderRadius.circular(0),
child: ButtonFill(
text: 'Simpan',
textColor: Colors.white,
onTap: () {},
bottomNavigationBar: Obx(
() => CustomeShadowCotainner(
borderRadius: BorderRadius.circular(0),
child: ButtonFill(
text: _profileController.isUpdatingUserData.value ? 'Menyimpan...' : 'Simpan',
textColor: Colors.white,
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',
value: userData.name ?? '-',
isDivider: false,
onTap: () {
onTap: () async {
Get.toNamed(Routes.CHANGEDATACOMPLETE);
},
),