This commit is contained in:
Hamza-Ayed
2025-09-08 00:19:57 +03:00
parent 8c7f3e3a75
commit 7e904ae460
146 changed files with 761 additions and 327 deletions

View File

@@ -1,21 +1,20 @@
import 'dart:convert';
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/firebase/local_notification.dart';
import 'package:sefer_driver/controller/functions/crud.dart';
import 'package:sefer_driver/controller/functions/encrypt_decrypt.dart';
import 'package:sefer_driver/controller/home/payment/captain_wallet_controller.dart';
import 'package:sefer_driver/views/widgets/mydialoug.dart';
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 '../../../main.dart';
import '../../../views/widgets/error_snakbar.dart';
import '../../firebase/local_notification.dart';
import '../../functions/launch.dart';
import '../../notification/notification_captain_controller.dart';
@@ -26,6 +25,10 @@ class InviteController extends GetxController {
String? couponCode;
String? driverCouponCode;
// **FIX**: Added the missing 'contacts' and 'contactMaps' definitions.
List<Contact> contacts = [];
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
int selectedTab = 0;
PassengerStats passengerStats = PassengerStats();
void updateSelectedTab(int index) {
@@ -33,14 +36,10 @@ class InviteController extends GetxController {
update();
}
Future<void> shareCouponCode() async {
// TODO: Implement sharing functionality
// You can use share_plus package to share the coupon code
}
Future<void> shareDriverCode() async {
if (driverCouponCode != null) {
final String shareText = '''
Join Intaleq as a driver using my referral code!
final String shareText =
'''Join Intaleq as a driver using my referral code!
Use code: $driverCouponCode
Download the Intaleq Driver app now and earn rewards!
''';
@@ -50,8 +49,7 @@ Download the Intaleq Driver app now and earn rewards!
Future<void> sharePassengerCode() async {
if (couponCode != null) {
final String shareText = '''
Get a discount on your first Intaleq ride!
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!
''';
@@ -62,9 +60,98 @@ Download the Intaleq app now and enjoy your ride!
@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<void> 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<Contact> 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) {
await 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<void> 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: {
@@ -75,7 +162,9 @@ Download the Intaleq app now and enjoy your ride!
driverInvitationData = data['message'];
update();
}
} catch (e) {}
} catch (e) {
// Handle error gracefully
}
}
void fetchDriverStatsPassengers() async {
@@ -89,61 +178,8 @@ Download the Intaleq app now and enjoy your ride!
driverInvitationDataToPassengers = data['message'];
update();
}
} catch (e) {}
}
void selectPhone(String phone) {
if (box.read(BoxName.countryCode) == 'Egypt') {
invitePhoneController.text = phone;
update();
Get.back();
}
}
Future<void> saveContactsToServer() async {
try {
// TODO: Implement the actual server upload logic here
// Simulating a server request
await Future.delayed(Duration(seconds: 2));
mySnackbarSuccess(
'${selectedContacts.length} contacts saved to server'.tr);
} catch (e) {
mySnackeBarError(
'An error occurred while saving contacts to server: $e'.tr);
}
}
List<Contact> contacts = <Contact>[];
List<Contact> selectedContacts = <Contact>[];
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
Future<void> pickContacts() async {
try {
if (await FlutterContacts.requestPermission(readonly: true)) {
final List<Contact> fetchedContacts =
await FlutterContacts.getContacts(withProperties: true);
contacts = fetchedContacts;
// Convert contacts to a list of maps
contactMaps.value = fetchedContacts.map((contact) {
return {
'name': contact.displayName,
'phones':
contact.phones.map((phone) => phone.normalizedNumber).toList(),
'emails': contact.emails.map((email) => email.address).toList(),
};
}).toList();
update();
if (contacts.isEmpty) {
mySnackeBarError('Please add contacts to your phone.'.tr);
}
} else {
mySnackeBarError('Contact permission is required to pick contacts'.tr);
}
} catch (e) {
mySnackeBarError('An error occurred while picking contacts: $e'.tr);
// Handle error gracefully
}
}
@@ -158,14 +194,10 @@ Download the Intaleq app now and enjoy your ride!
if (int.parse((driverInvitationData[index]['countOfInvitDriver'])) <
100) {
Get.back();
} else
//claim your gift
if (isAvailable) {
// Authenticate the user
} else if (isAvailable) {
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: AuthenticationOptions(
options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
@@ -178,46 +210,39 @@ Download the Intaleq app now and enjoy your ride!
payload: {'id': (driverInvitationData[index]['id'])});
await Get.find<CaptainWalletController>().addDriverPayment(
'paymentMethod',
('500'),
('50000'),
'',
);
// add for invitor too
await Get.find<CaptainWalletController>()
.addDriverWalletToInvitor(
'paymentMethod',
(driverInvitationData[index]['driverInviterId']),
('500'),
('50000'),
);
// await Get.find<CaptainWalletController>()
// .addSeferWallet('giftInvitation', ('-1000'));
NotificationCaptainController().addNotificationCaptain(
driverInvitationData[index]['driverInviterId'].toString(),
"You have got a gift for invitation".tr,
'${"You have 500".tr} ${'LE'.tr}',
'${"You have 50000".tr} ${'SYP'.tr}',
false);
NotificationController().showNotification(
"You have got a gift for invitation".tr,
'${"You have 500".tr} ${'LE'.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();
});
"Share the app with another new driver".tr, () => Get.back());
}
} else {
// Authentication failed, handle accordingly
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
Get.back();
});
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();
});
MyDialog().getDialog(
'Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() => Get.back());
}
},
);
@@ -239,38 +264,31 @@ Download the Intaleq app now and enjoy your ride!
3) {
Get.back();
} else if (isAvailable) {
// Authenticate the user
bool didAuthenticate = await LocalAuthentication().authenticate(
localizedReason: 'Use Touch ID or Face ID to confirm payment',
options: AuthenticationOptions(
options: const AuthenticationOptions(
biometricOnly: true,
sensitiveTransaction: true,
));
if (didAuthenticate) {
// Claim the gift if 100 trips are completed
if (driverInvitationDataToPassengers[index]['isGiftToken']
.toString() ==
'0') {
Get.back();
// Add wallet to the inviter
await Get.find<CaptainWalletController>()
.addDriverWallet('paymentMethod', '50', '50');
// add for invitor too
.addDriverWallet('paymentMethod', '20000', '20000');
await Get.find<CaptainWalletController>()
.addDriverWalletToInvitor('paymentMethod',
driverInvitationData[index]['driverInviterId'], '50');
// Update invitation as claimed
driverInvitationData[index]['driverInviterId'], '20000');
await CRUD().post(
link: AppLink.updatePassengerGift,
payload: {'id': driverInvitationDataToPassengers[index]['id']},
);
// Notify the inviter
NotificationCaptainController().addNotificationCaptain(
driverInvitationDataToPassengers[index]['passengerInviterId']
.toString(),
"You have got a gift for invitation".tr,
'${"You have 50".tr} ${'LE'}',
'${"You have 20000".tr} ${'SYP'.tr}',
false,
);
} else {
@@ -278,66 +296,54 @@ Download the Intaleq app now and enjoy your ride!
MyDialog().getDialog(
"You have got a gift".tr,
"Share the app with another new passenger".tr,
() {
Get.back();
},
() => Get.back(),
);
}
} else {
// Authentication failed, handle accordingly
MyDialog().getDialog('Authentication failed'.tr, ''.tr, () {
Get.back();
});
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();
});
MyDialog().getDialog(
'Biometric Authentication'.tr,
'You should use Touch ID or Face ID to confirm payment'.tr,
() => Get.back());
}
},
);
}
savePhoneToServer() async {
for (var i = 0; i < contactMaps.length; i++) {
var phones = contactMaps[i]['phones'];
if (phones != null && phones.isNotEmpty && phones[0].isNotEmpty) {
var res = await CRUD().post(link: AppLink.savePhones, payload: {
"name": contactMaps[i]['name'] ?? 'none',
"phones": phones[0] ?? 'none',
"phones2": phones.join(', ') ??
'none', // Convert List<String> to a comma-separated string
});
if (res != 'failure') {}
} else {}
}
}
String formatPhoneNumber(String input) {
// Remove any non-digit characters
/// Formats a phone number to the standard Syrian international format (+963...).
String _formatSyrianPhoneNumber(String input) {
String digitsOnly = input.replaceAll(RegExp(r'\D'), '');
// Ensure the number starts with the country code
if (digitsOnly.startsWith('20')) {
digitsOnly = digitsOnly.substring(1);
if (digitsOnly.startsWith('963')) {
return '+$digitsOnly';
}
return digitsOnly;
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
}
/// Sends an invitation to a potential new driver.
void sendInvite() async {
if (invitePhoneController.text.isEmpty) {
mySnackeBarError('Please enter an phone address'.tr);
mySnackeBarError('Please enter a phone number'.tr);
return;
}
String formattedPhoneNumber =
_formatSyrianPhoneNumber(invitePhoneController.text);
if (formattedPhoneNumber.length < 13) {
mySnackeBarError('Please enter a correct phone'.tr);
return;
}
// try {
String phoneNumber = formatPhoneNumber(invitePhoneController.text);
var response = await CRUD().post(link: AppLink.addInviteDriver, payload: {
"driverId": box.read(BoxName.driverID),
"inviterDriverPhone": '+2$phoneNumber'
"inviterDriverPhone": formattedPhoneNumber,
});
if (response != 'failure') {
@@ -353,37 +359,37 @@ Download the Intaleq app now and enjoy your ride!
'*Android:* https://play.google.com/store/apps/details?id=com.sefer_driver\n\n\n'
'*iOS:* https://apps.apple.com/ae/app/sefer-driver/id6502189302';
launchCommunication('whatsapp', '+2$phoneNumber', message);
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 an phone address'.tr);
mySnackeBarError('Please enter a phone number'.tr);
return;
}
String formattedPhoneNumber =
_formatSyrianPhoneNumber(invitePhoneController.text);
if (formattedPhoneNumber.length < 13) {
mySnackeBarError('Please enter a correct phone'.tr);
return;
}
// try {
String phoneNumber = formatPhoneNumber(invitePhoneController.text);
var response =
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
"driverId": box.read(BoxName.driverID),
"inviterPassengerPhone": '+2$phoneNumber'
"inviterPassengerPhone": formattedPhoneNumber,
});
if (response != 'failure') {
var d = jsonDecode(response);
mySnackbarSuccess('Invite sent successfully'.tr);
String message = '${'*Intaleq APP CODE*'.tr}\n\n'
'${"Use this code in registration".tr}\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'
@@ -392,13 +398,10 @@ Download the Intaleq app now and enjoy your ride!
'*Android:* https://play.google.com/store/apps/details?id=com.mobileapp.store.ride\n\n\n'
'*iOS:* https://apps.apple.com/us/app/sefer/id6458734951';
launchCommunication('whatsapp', '+2$phoneNumber', message);
launchCommunication('whatsapp', formattedPhoneNumber, message);
invitePhoneController.clear();
} else {
mySnackeBarError(
"Invite code already used".tr,
);
mySnackeBarError("Invite code already used".tr);
}
}
}

View File

@@ -338,6 +338,7 @@ class LoginDriverController extends GetxController {
if ((jsonDecode(token)['data'][0]['token'].toString()) !=
box.read(BoxName.tokenDriver).toString()) {
await Get.defaultDialog(
barrierDismissible: false,
title: 'Device Change Detected'.tr,
middleText: 'Please verify your identity'.tr,
textConfirm: 'Verify'.tr,