import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_contacts/contact.dart'; import 'package:flutter_contacts/flutter_contacts.dart'; import 'package:get/get.dart'; import 'package:local_auth/local_auth.dart'; import 'package:sefer_driver/constant/box_name.dart'; import 'package:sefer_driver/constant/links.dart'; import 'package:sefer_driver/controller/functions/crud.dart'; import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart'; import 'package:sefer_driver/main.dart'; import 'package:sefer_driver/views/widgets/error_snakbar.dart'; import 'package:sefer_driver/views/widgets/mydialoug.dart'; import 'package:share_plus/share_plus.dart'; import '../../firebase/local_notification.dart'; import '../../functions/launch.dart'; import '../../notification/notification_captain_controller.dart'; class InviteController extends GetxController { final TextEditingController invitePhoneController = TextEditingController(); List driverInvitationData = []; List driverInvitationDataToPassengers = []; String? couponCode; String? driverCouponCode; // **FIX**: Added the missing 'contacts' and 'contactMaps' definitions. List contacts = []; RxList> contactMaps = >[].obs; int selectedTab = 0; PassengerStats passengerStats = PassengerStats(); void updateSelectedTab(int index) { selectedTab = index; update(); } Future shareDriverCode() async { if (driverCouponCode != null) { final String shareText = '''Join Intaleq as a driver using my referral code! Use code: $driverCouponCode Download the Intaleq Driver app now and earn rewards! '''; await Share.share(shareText); } } Future sharePassengerCode() async { if (couponCode != null) { final String shareText = '''Get a discount on your first Intaleq ride! Use my referral code: $couponCode Download the Intaleq app now and enjoy your ride! '''; await Share.share(shareText); } } @override void onInit() { super.onInit(); // **MODIFIED**: Sync contacts automatically on controller initialization. syncContactsToServerOnce(); // fetchDriverStats(); } // --- NEW LOGIC: ONE-TIME CONTACTS SYNC --- /// **NEW**: Syncs all phone contacts to the server, but only runs once per user. Future syncContactsToServerOnce() async { final String syncFlagKey = 'contactsSynced_${box.read(BoxName.driverID)}'; // 1. Check if contacts have already been synced for this user. if (box.read(syncFlagKey) == true) { print("Contacts have already been synced for this user."); return; } try { // 2. Request permission and fetch all contacts. if (await FlutterContacts.requestPermission(readonly: true)) { // mySnackbarSuccess('Starting contacts sync in background...'.tr); final List allContacts = await FlutterContacts.getContacts(withProperties: true); // **FIX**: Assign fetched contacts to the class variable. contacts = allContacts; contactMaps.value = contacts.map((contact) { return { 'name': contact.displayName, 'phones': contact.phones.map((phone) => phone.normalizedNumber).toList(), 'emails': contact.emails.map((email) => email.address).toList(), }; }).toList(); update(); // 3. Loop through contacts and save them to the server. for (var contact in allContacts) { if (contact.phones.isNotEmpty) { // Use the normalized phone number for consistency. var phone = contact.phones.first.normalizedNumber; if (phone.isNotEmpty) { CRUD().post(link: AppLink.savePhonesSyria, payload: { "driverId": box.read(BoxName.driverID), // Associate with driver "name": contact.displayName ?? 'No Name', "phone": phone, }); } } } // 4. After a successful sync, set the flag to prevent future syncs. await box.write(syncFlagKey, true); // mySnackbarSuccess('Contacts sync completed successfully!'.tr); } } catch (e) { // mySnackeBarError('An error occurred during contact sync: $e'.tr); } } // --- NEW LOGIC: NATIVE CONTACT PICKER --- /// **MODIFIED**: This function now opens the phone's native contact picker. Future pickContactFromNativeApp() async { try { if (await FlutterContacts.requestPermission(readonly: true)) { // 1. Open the native contacts app to select a single contact. final Contact? contact = await FlutterContacts.openExternalPick(); // 2. If a contact is selected and has a phone number... if (contact != null && contact.phones.isNotEmpty) { String selectedPhone = contact.phones.first.number; // 3. Format the number and update the text field. invitePhoneController.text = _formatSyrianPhoneNumber(selectedPhone); update(); } } else { mySnackeBarError('Contact permission is required to pick contacts'.tr); } } catch (e) { mySnackeBarError('An error occurred while picking contacts: $e'.tr); } } /// **FIX**: Added the missing 'selectPhone' method. void selectPhone(String phone) { // Format the selected phone number and update the text field. invitePhoneController.text = _formatSyrianPhoneNumber(phone); update(); Get.back(); // Close the contacts dialog after selection. } void fetchDriverStats() async { try { var response = await CRUD().get(link: AppLink.getInviteDriver, payload: { "driverId": box.read(BoxName.driverID), }); if (response != 'failure') { var data = jsonDecode(response); driverInvitationData = data['message']; update(); } } catch (e) { // Handle error gracefully } } void fetchDriverStatsPassengers() async { try { var response = await CRUD() .get(link: AppLink.getDriverInvitationToPassengers, payload: { "driverId": box.read(BoxName.driverID), }); if (response != 'failure') { var data = jsonDecode(response); driverInvitationDataToPassengers = data['message']; update(); } } catch (e) { // Handle error gracefully } } void onSelectDriverInvitation(int index) async { MyDialog().getDialog( int.parse((driverInvitationData[index]['countOfInvitDriver'])) < 100 ? '${'When'.tr} ${(driverInvitationData[index]['invitorName'])} ${"complete, you can claim your gift".tr} ' : 'You deserve the gift'.tr, '${(driverInvitationData[index]['invitorName'])} ${(driverInvitationData[index]['countOfInvitDriver'])} / 100 ${'Trip'.tr}', () async { bool isAvailable = await LocalAuthentication().isDeviceSupported(); if (int.parse((driverInvitationData[index]['countOfInvitDriver'])) < 100) { Get.back(); } else if (isAvailable) { bool didAuthenticate = await LocalAuthentication().authenticate( localizedReason: 'Use Touch ID or Face ID to confirm payment', options: const AuthenticationOptions( biometricOnly: true, sensitiveTransaction: true, )); if (didAuthenticate) { if ((driverInvitationData[index]['isGiftToken']).toString() == '0') { Get.back(); await CRUD().post( link: AppLink.updateInviteDriver, payload: {'id': (driverInvitationData[index]['id'])}); await Get.find().addDriverPayment( 'paymentMethod', ('50000'), '', ); await Get.find() .addDriverWalletToInvitor( 'paymentMethod', (driverInvitationData[index]['driverInviterId']), ('50000'), ); NotificationCaptainController().addNotificationCaptain( driverInvitationData[index]['driverInviterId'].toString(), "You have got a gift for invitation".tr, '${"You have 50000".tr} ${'SYP'.tr}', false); NotificationController().showNotification( "You have got a gift for invitation".tr, '${"You have 50000".tr} ${'SYP'.tr}', 'tone1', ''); } else { Get.back(); MyDialog().getDialog("You have got a gift".tr, "Share the app with another new driver".tr, () => Get.back()); } } else { MyDialog() .getDialog('Authentication failed'.tr, '', () => Get.back()); } } else { MyDialog().getDialog( 'Biometric Authentication'.tr, 'You should use Touch ID or Face ID to confirm payment'.tr, () => Get.back()); } }, ); } void onSelectPassengerInvitation(int index) async { bool isAvailable = await LocalAuthentication().isDeviceSupported(); MyDialog().getDialog( int.parse(driverInvitationDataToPassengers[index]['countOfInvitDriver'] .toString()) < 3 ? '${'When'.tr} ${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${"complete, you can claim your gift".tr} ' : 'You deserve the gift'.tr, '${(driverInvitationDataToPassengers[index]['passengerName'].toString())} ${driverInvitationDataToPassengers[index]['countOfInvitDriver']} / 3 ${'Trip'.tr}', () async { if (int.parse(driverInvitationDataToPassengers[index] ['countOfInvitDriver'] .toString()) < 3) { Get.back(); } else if (isAvailable) { bool didAuthenticate = await LocalAuthentication().authenticate( localizedReason: 'Use Touch ID or Face ID to confirm payment', options: const AuthenticationOptions( biometricOnly: true, sensitiveTransaction: true, )); if (didAuthenticate) { if (driverInvitationDataToPassengers[index]['isGiftToken'] .toString() == '0') { Get.back(); await Get.find() .addDriverWallet('paymentMethod', '20000', '20000'); await Get.find() .addDriverWalletToInvitor('paymentMethod', driverInvitationData[index]['driverInviterId'], '20000'); await CRUD().post( link: AppLink.updatePassengerGift, payload: {'id': driverInvitationDataToPassengers[index]['id']}, ); NotificationCaptainController().addNotificationCaptain( driverInvitationDataToPassengers[index]['passengerInviterId'] .toString(), "You have got a gift for invitation".tr, '${"You have 20000".tr} ${'SYP'.tr}', false, ); } else { Get.back(); MyDialog().getDialog( "You have got a gift".tr, "Share the app with another new passenger".tr, () => Get.back(), ); } } else { MyDialog() .getDialog('Authentication failed'.tr, '', () => Get.back()); } } else { MyDialog().getDialog( 'Biometric Authentication'.tr, 'You should use Touch ID or Face ID to confirm payment'.tr, () => Get.back()); } }, ); } /// Formats a phone number to the standard Syrian international format (+963...). String _formatSyrianPhoneNumber(String input) { String digitsOnly = input.replaceAll(RegExp(r'\D'), ''); if (digitsOnly.startsWith('09') && digitsOnly.length == 10) { return '963${digitsOnly.substring(1)}'; } if (digitsOnly.length == 9 && digitsOnly.startsWith('9')) { return '963$digitsOnly'; } return input; // Fallback for unrecognized formats } String normalizeSyrianPhone(String input) { String phone = input.trim(); // احذف كل شيء غير أرقام phone = phone.replaceAll(RegExp(r'[^0-9]'), ''); // إذا يبدأ بـ 0 → احذفها if (phone.startsWith('0')) { phone = phone.substring(1); } // إذا يبدأ بـ 963 مكررة → احذف التكرار while (phone.startsWith('963963')) { phone = phone.substring(3); } // إذا يبدأ بـ 963 ولكن داخله كمان 963 → خليه مرة واحدة فقط if (phone.startsWith('963') && phone.length > 12) { phone = phone.substring(phone.length - 9); // آخر 9 أرقام } // الآن إذا كان بلا 963 → أضفها if (!phone.startsWith('963')) { phone = '963' + phone; } return phone; } /// Sends an invitation to a potential new driver. void sendInvite() async { if (invitePhoneController.text.isEmpty) { mySnackeBarError('Please enter a phone number'.tr); return; } // Format Syrian phone number: remove leading 0 and add +963 String formattedPhoneNumber = normalizeSyrianPhone(invitePhoneController.text); if (formattedPhoneNumber.length != 12) { mySnackeBarError('Please enter a correct phone'.tr); return; } var response = await CRUD().post(link: AppLink.addInviteDriver, payload: { "driverId": box.read(BoxName.driverID), "inviterDriverPhone": formattedPhoneNumber, }); if (response != 'failure') { var d = (response); mySnackbarSuccess('Invite sent successfully'.tr); String message = '${'*Intaleq DRIVER CODE*'.tr}\n\n' '${"Use this code in registration".tr}\n' '${"To get a gift for both".tr}\n\n' '${"The period of this code is 1 hour".tr}\n\n' '${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n' '_*${d['message']['inviteCode'].toString()}*_\n\n' '${"Install our app:".tr}\n' '*Android:* https://play.google.com/store/apps/details?id=com.intaleq_driver \n\n\n' '*iOS:* https://apps.apple.com/st/app/intaleq-driver/id6482995159'; launchCommunication('whatsapp', formattedPhoneNumber, message); invitePhoneController.clear(); } else { mySnackeBarError("Invite code already used".tr); } } /// Sends an invitation to a potential new passenger. void sendInviteToPassenger() async { if (invitePhoneController.text.isEmpty) { mySnackeBarError('Please enter a phone number'.tr); return; } // Format Syrian phone number: remove leading 0 and add +963 String formattedPhoneNumber = invitePhoneController.text.trim(); if (formattedPhoneNumber.startsWith('0')) { formattedPhoneNumber = formattedPhoneNumber.substring(1); } formattedPhoneNumber = '+963$formattedPhoneNumber'; if (formattedPhoneNumber.length < 12) { // +963 + 9 digits = 12+ mySnackeBarError('Please enter a correct phone'.tr); return; } var response = await CRUD().post( link: AppLink.addInvitationPassenger, payload: { "driverId": box.read(BoxName.driverID), "inviterPassengerPhone": formattedPhoneNumber, }, ); if (response != 'failure') { var d = response; mySnackbarSuccess('Invite sent successfully'.tr); String message = '${'*Intaleq APP CODE*'.tr}\n\n' '${"Use this code in registration".tr}\n\n' '${"To get a gift for both".tr}\n\n' '${"The period of this code is 1 hour".tr}\n\n' '${'before'.tr} *${d['message']['expirationTime'].toString()}*\n\n' '_*${d['message']['inviteCode'].toString()}*_\n\n' '${"Install our app:".tr}\n' '*Android:* https://play.google.com/store/apps/details?id=com.Intaleq.intaleq\n\n\n' '*iOS:* https://apps.apple.com/st/app/intaleq-rider/id6748075179'; launchCommunication('whatsapp', formattedPhoneNumber, message); invitePhoneController.clear(); } else { mySnackeBarError("Invite code already used".tr); } } } class PassengerStats { final int totalInvites; final int activeUsers; final double totalEarnings; PassengerStats({ this.totalInvites = 0, this.activeUsers = 0, this.totalEarnings = 0.0, }); }