313 lines
11 KiB
Dart
313 lines
11 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:Intaleq/constant/box_name.dart';
|
|
import 'package:Intaleq/constant/colors.dart';
|
|
import 'package:Intaleq/constant/links.dart';
|
|
import 'package:Intaleq/controller/functions/crud.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:share_plus/share_plus.dart';
|
|
|
|
import '../../../main.dart';
|
|
import '../../../print.dart';
|
|
import '../../../views/widgets/error_snakbar.dart';
|
|
import '../../../views/widgets/mydialoug.dart';
|
|
import '../../functions/launch.dart';
|
|
import '../../notification/notification_captain_controller.dart';
|
|
import '../../payment/payment_controller.dart';
|
|
|
|
class InviteController extends GetxController {
|
|
final TextEditingController invitePhoneController = TextEditingController();
|
|
List driverInvitationData = [];
|
|
List driverInvitationDataToPassengers = [];
|
|
String? couponCode;
|
|
String? driverCouponCode;
|
|
|
|
int selectedTab = 0;
|
|
PassengerStats passengerStats = PassengerStats();
|
|
|
|
List<Contact> contacts = <Contact>[];
|
|
RxList<Map<String, dynamic>> contactMaps = <Map<String, dynamic>>[].obs;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
// It's good practice to fetch initial data in onInit or onReady
|
|
// fetchDriverStats();
|
|
// fetchDriverStatsPassengers();
|
|
}
|
|
|
|
void updateSelectedTab(int index) {
|
|
selectedTab = index;
|
|
update();
|
|
}
|
|
|
|
// --- Sharing Methods ---
|
|
|
|
Future<void> shareDriverCode() async {
|
|
if (driverCouponCode != null) {
|
|
final String shareText = '''
|
|
${'Join Intaleq as a driver using my referral code!'.tr}
|
|
${'Use code:'.tr} $driverCouponCode
|
|
${'Download the Intaleq Driver app now and earn rewards!'.tr}
|
|
''';
|
|
await Share.share(shareText);
|
|
}
|
|
}
|
|
|
|
Future<void> sharePassengerCode() async {
|
|
if (couponCode != null) {
|
|
final String shareText = '''
|
|
${'Get a discount on your first Intaleq ride!'.tr}
|
|
${'Use my referral code:'.tr} $couponCode
|
|
${'Download the Intaleq app now and enjoy your ride!'.tr}
|
|
''';
|
|
await Share.share(shareText);
|
|
}
|
|
}
|
|
|
|
// --- Data Fetching ---
|
|
|
|
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) {
|
|
Log.print("Error fetching driver stats: $e");
|
|
}
|
|
}
|
|
|
|
void fetchDriverStatsPassengers() async {
|
|
try {
|
|
var response = await CRUD()
|
|
.get(link: AppLink.getDriverInvitationToPassengers, payload: {
|
|
"driverId": box.read(BoxName.passengerID),
|
|
});
|
|
if (response != 'failure') {
|
|
var data = jsonDecode(response);
|
|
driverInvitationDataToPassengers = data['message'];
|
|
update();
|
|
}
|
|
} catch (e) {
|
|
Log.print("Error fetching passenger stats: $e");
|
|
}
|
|
}
|
|
|
|
// --- Contact Handling ---
|
|
|
|
/// **IMPROVEMENT**: This function now filters out contacts without any phone numbers.
|
|
/// This is the fix for the `RangeError` you were seeing, which happened when the UI
|
|
/// tried to access the first phone number of a contact that had none.
|
|
Future<void> pickContacts() async {
|
|
try {
|
|
if (await FlutterContacts.requestPermission(readonly: true)) {
|
|
final List<Contact> allContacts =
|
|
await FlutterContacts.getContacts(withProperties: true);
|
|
final int totalContactsOnDevice = allContacts.length;
|
|
|
|
// **FIX**: Filter contacts to only include those with at least one phone number.
|
|
contacts = allContacts.where((c) => c.phones.isNotEmpty).toList();
|
|
final int contactsWithPhones = contacts.length;
|
|
|
|
if (contactsWithPhones > 0) {
|
|
Log.print('Found $contactsWithPhones contacts with phone numbers.');
|
|
contactMaps.value = contacts.map((contact) {
|
|
return {
|
|
'name': contact.displayName,
|
|
'phones': contact.phones.map((p) => p.number).toList(),
|
|
'emails': contact.emails.map((e) => e.address).toList(),
|
|
};
|
|
}).toList();
|
|
update();
|
|
|
|
// **IMPROVEMENT**: Provide feedback if some contacts were filtered out.
|
|
if (contactsWithPhones < totalContactsOnDevice) {
|
|
Get.snackbar('Contacts Loaded'.tr,
|
|
'${'Showing'.tr} $contactsWithPhones ${'of'.tr} $totalContactsOnDevice ${'contacts. Others were hidden because they don\'t have a phone number.'.tr}',
|
|
snackPosition: SnackPosition.BOTTOM);
|
|
}
|
|
} else {
|
|
Get.snackbar('No contacts found'.tr,
|
|
'No contacts with phone numbers were found on your device.'.tr);
|
|
}
|
|
} else {
|
|
Get.snackbar('Permission denied'.tr,
|
|
'Contact permission is required to pick contacts'.tr);
|
|
}
|
|
} catch (e) {
|
|
Log.print('Error picking contacts: $e');
|
|
Get.snackbar(
|
|
'Error'.tr, 'An error occurred while picking contacts: $e'.tr);
|
|
}
|
|
}
|
|
|
|
void selectPhone(String phone) {
|
|
invitePhoneController.text = phone;
|
|
update();
|
|
Get.back();
|
|
}
|
|
|
|
/// **IMPROVEMENT**: A new robust function to format phone numbers specifically for Syria (+963).
|
|
/// It handles various user inputs gracefully to produce a standardized international format.
|
|
String _formatSyrianPhoneNumber(String phone) {
|
|
// 1. Remove all non-digit characters to clean the input.
|
|
String digitsOnly = phone.replaceAll(RegExp(r'\D'), '');
|
|
|
|
// 2. If it already starts with the country code, we assume it's correct.
|
|
if (digitsOnly.startsWith('963')) {
|
|
return '$digitsOnly';
|
|
}
|
|
|
|
// 3. If it starts with '09' (common local format), remove the leading '0'.
|
|
if (digitsOnly.startsWith('09')) {
|
|
digitsOnly = digitsOnly.substring(1);
|
|
}
|
|
|
|
// 4. Prepend the Syrian country code.
|
|
return '963$digitsOnly';
|
|
}
|
|
|
|
/// **IMPROVEMENT**: This method now uses the new phone formatting logic and
|
|
/// sends a much-improved, user-friendly WhatsApp message.
|
|
void sendInviteToPassenger() async {
|
|
if (invitePhoneController.text.isEmpty ||
|
|
invitePhoneController.text.length < 9) {
|
|
mySnackeBarError('Please enter a correct phone'.tr);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Use the new formatting function to ensure the number is correct.
|
|
String formattedPhoneNumber =
|
|
_formatSyrianPhoneNumber(invitePhoneController.text);
|
|
|
|
var response =
|
|
await CRUD().post(link: AppLink.addInvitationPassenger, payload: {
|
|
"driverId": box.read(BoxName.passengerID),
|
|
"inviterPassengerPhone": formattedPhoneNumber,
|
|
});
|
|
|
|
if (response != 'failure') {
|
|
var d = response;
|
|
Get.snackbar('Success'.tr, 'Invite sent successfully'.tr,
|
|
backgroundColor: Colors.green, colorText: Colors.white);
|
|
|
|
String expirationTime = d['message']['expirationTime'].toString();
|
|
String inviteCode = d['message']['inviteCode'].toString();
|
|
|
|
// New and improved WhatsApp message for better user engagement.
|
|
String message =
|
|
"👋 ${'Hello! I\'m inviting you to try Intaleq.'.tr}\n\n"
|
|
"🎁 ${'Use my invitation code to get a special gift on your first ride!'.tr}\n\n"
|
|
"${'Your personal invitation code is:'.tr}\n"
|
|
"*$inviteCode*\n\n"
|
|
"⏳ ${'Be sure to use it quickly! This code expires at'.tr} *$expirationTime*.\n\n"
|
|
"📲 ${'Download the app now:'.tr}\n"
|
|
"• *Android:* https://play.google.com/store/apps/details?id=com.Intaleq.intaleq\n"
|
|
"• *iOS:* https://apps.apple.com/st/app/intaleq-rider/id6748075179\n\n"
|
|
"${'See you on the road!'.tr} 🚗";
|
|
|
|
launchCommunication('whatsapp', formattedPhoneNumber, message);
|
|
invitePhoneController.clear();
|
|
update();
|
|
} else {
|
|
Get.snackbar(
|
|
'Error'.tr, "This phone number has already been invited.".tr,
|
|
backgroundColor: AppColor.redColor,
|
|
duration: const Duration(seconds: 4));
|
|
}
|
|
} catch (e) {
|
|
Log.print("Error sending invite: $e");
|
|
Get.snackbar(
|
|
'Error'.tr, 'An unexpected error occurred. Please try again.'.tr,
|
|
backgroundColor: AppColor.redColor);
|
|
}
|
|
}
|
|
|
|
// This function is dependent on the `pickContacts` method filtering out contacts without phones.
|
|
savePhoneToServer() async {
|
|
for (var contactMap in contactMaps) {
|
|
// The `pickContacts` function ensures the 'phones' list is not empty here.
|
|
var phones = contactMap['phones'] as List<String>;
|
|
var res = await CRUD().post(link: AppLink.savePhones, payload: {
|
|
"name": contactMap['name'] ?? 'No Name',
|
|
"phones": phones.first, // Safely access the first phone number
|
|
"phones2": phones.join(', '),
|
|
});
|
|
if (res == 'failure') {
|
|
Log.print('Failed to save contact: ${contactMap['name']}');
|
|
}
|
|
}
|
|
}
|
|
|
|
void onSelectPassengerInvitation(int index) async {
|
|
try {
|
|
final invitation = driverInvitationDataToPassengers[index];
|
|
final tripCount =
|
|
int.tryParse(invitation['countOfInvitDriver'].toString()) ?? 0;
|
|
final passengerName = invitation['passengerName'].toString();
|
|
final isGiftTaken = invitation['isGiftToken'].toString() == '1';
|
|
|
|
if (tripCount >= 2) {
|
|
// Gift can be claimed
|
|
if (!isGiftTaken) {
|
|
MyDialog().getDialog(
|
|
'You deserve the gift'.tr,
|
|
'${'Claim your 20 LE gift for inviting'.tr} $passengerName!',
|
|
() async {
|
|
Get.back(); // Close dialog first
|
|
await Get.find<PaymentController>().addPassengersWallet('20');
|
|
await CRUD().post(
|
|
link: AppLink.updatePassengerGift,
|
|
payload: {'id': invitation['id']},
|
|
);
|
|
NotificationCaptainController().addNotificationCaptain(
|
|
invitation['passengerInviterId'].toString(),
|
|
"You have got a gift for invitation".tr,
|
|
'${"You have earned 20".tr} ${'LE'}',
|
|
false,
|
|
);
|
|
fetchDriverStatsPassengers(); // Refresh list
|
|
},
|
|
);
|
|
} else {
|
|
MyDialog().getDialog(
|
|
"Gift Already Claimed".tr,
|
|
"You have already received your gift for inviting $passengerName."
|
|
.tr,
|
|
() => Get.back(),
|
|
);
|
|
}
|
|
} else {
|
|
// Gift not yet earned
|
|
MyDialog().getDialog(
|
|
'${'Keep it up!'.tr}',
|
|
'$passengerName ${'has completed'.tr} $tripCount / 2 ${'trips'.tr}. ${"You can claim your gift once they complete 2 trips.".tr}',
|
|
() => Get.back(),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
Log.print("Error in onSelectPassengerInvitation: $e");
|
|
}
|
|
}
|
|
}
|
|
|
|
class PassengerStats {
|
|
final int totalInvites;
|
|
final int activeUsers;
|
|
final double totalEarnings;
|
|
|
|
PassengerStats({
|
|
this.totalInvites = 0,
|
|
this.activeUsers = 0,
|
|
this.totalEarnings = 0.0,
|
|
});
|
|
}
|