2026-04-03-pre map libra
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_contacts/contact.dart';
|
||||
@@ -124,24 +125,175 @@ Download the Intaleq app now and enjoy your ride!
|
||||
/// **MODIFIED**: This function now opens the phone's native contact picker.
|
||||
Future<void> pickContactFromNativeApp() async {
|
||||
try {
|
||||
log('=== START: FETCHING ALL CONTACTS FOR BOTTOM SHEET ===',
|
||||
name: 'ContactPicker');
|
||||
|
||||
if (await FlutterContacts.requestPermission(readonly: true)) {
|
||||
// 1. Open the native contacts app to select a single contact.
|
||||
final Contact? contact = await FlutterContacts.openExternalPick();
|
||||
// عرض شاشة تحميل بسيطة ريثما يتم جلب الأسماء
|
||||
Get.dialog(const Center(child: CircularProgressIndicator()),
|
||||
barrierDismissible: false);
|
||||
|
||||
// 2. If a contact is selected and has a phone number...
|
||||
if (contact != null && contact.phones.isNotEmpty) {
|
||||
String selectedPhone = contact.phones.first.number;
|
||||
log('Permission granted. Calling FlutterContacts.getContacts()...',
|
||||
name: 'ContactPicker');
|
||||
|
||||
// 3. Format the number and update the text field.
|
||||
invitePhoneController.text = _formatSyrianPhoneNumber(selectedPhone);
|
||||
update();
|
||||
// جلب جميع جهات الاتصال إجبارياً من الصفر مع خصائصها
|
||||
List<Contact> allContacts =
|
||||
await FlutterContacts.getContacts(withProperties: true);
|
||||
|
||||
log('Total Contacts Fetched from Device: ${allContacts.length}',
|
||||
name: 'ContactPicker');
|
||||
|
||||
// فصل الأسماء لمعرفة الخلل
|
||||
List<Contact> validContacts = [];
|
||||
List<Contact> invalidContacts = [];
|
||||
|
||||
for (var c in allContacts) {
|
||||
if (c.phones.isNotEmpty) {
|
||||
validContacts.add(c);
|
||||
} else {
|
||||
invalidContacts.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
log('Contacts WITH phone numbers: ${validContacts.length}',
|
||||
name: 'ContactPicker');
|
||||
log('Contacts WITHOUT phone numbers: ${invalidContacts.length}',
|
||||
name: 'ContactPicker');
|
||||
|
||||
// طباعة أول 20 اسم صالح
|
||||
log('--- Sample of VALID Contacts ---', name: 'ContactPicker');
|
||||
for (int i = 0; i < validContacts.length && i < 20; i++) {
|
||||
log('[$i] Name: ${validContacts[i].displayName}, Phone: ${validContacts[i].phones.first.number}',
|
||||
name: 'ContactPicker');
|
||||
}
|
||||
|
||||
// طباعة أول 20 اسم غير صالح (بدون أرقام) لفحص المشكلة
|
||||
log('--- Sample of INVALID Contacts (No Phone) ---',
|
||||
name: 'ContactPicker');
|
||||
for (int i = 0; i < invalidContacts.length && i < 20; i++) {
|
||||
log('[$i] Name: ${invalidContacts[i].displayName}',
|
||||
name: 'ContactPicker');
|
||||
}
|
||||
|
||||
Get.back(); // إغلاق شاشة التحميل
|
||||
|
||||
if (validContacts.isEmpty) {
|
||||
mySnackeBarError('No contacts with phone numbers found'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
// متغيرات للبحث داخل القائمة المنسدلة
|
||||
RxList<Contact> filteredContacts = validContacts.obs;
|
||||
TextEditingController searchController = TextEditingController();
|
||||
|
||||
// دالة لتنظيف النصوص من أي رموز معطوبة
|
||||
String sanitizeText(String input) {
|
||||
if (input.isEmpty) return '';
|
||||
return input
|
||||
.replaceAll(
|
||||
RegExp(r'[^\x00-\x7F\u0600-\u06FF\u08A0-\u08FF\p{L}\p{N}\s]'),
|
||||
'')
|
||||
.trim();
|
||||
}
|
||||
|
||||
// فتح دليل هاتف مخصص داخل التطبيق
|
||||
Get.bottomSheet(
|
||||
Container(
|
||||
height: Get.height * 0.85,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(Get.context!).scaffoldBackgroundColor,
|
||||
borderRadius:
|
||||
const BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(top: 10, bottom: 10),
|
||||
width: 50,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[400],
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
),
|
||||
Text("Select a Contact".tr,
|
||||
style: const TextStyle(
|
||||
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: "Search name or number...".tr,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 0),
|
||||
),
|
||||
onChanged: (value) {
|
||||
filteredContacts.value = validContacts.where((c) {
|
||||
final nameMatch = c.displayName
|
||||
.toLowerCase()
|
||||
.contains(value.toLowerCase());
|
||||
final phoneMatch =
|
||||
c.phones.first.number.contains(value);
|
||||
return nameMatch || phoneMatch;
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Obx(() => ListView.builder(
|
||||
itemCount: filteredContacts.length,
|
||||
itemBuilder: (context, index) {
|
||||
Contact c = filteredContacts[index];
|
||||
var firstPhone = c.phones.first;
|
||||
|
||||
String selectedPhone =
|
||||
firstPhone.normalizedNumber.isNotEmpty
|
||||
? firstPhone.normalizedNumber
|
||||
: firstPhone.number;
|
||||
|
||||
String safeName = sanitizeText(c.displayName);
|
||||
if (safeName.isEmpty) safeName = 'Unknown'.tr;
|
||||
|
||||
String safePhone = sanitizeText(selectedPhone);
|
||||
String initial = safeName.isNotEmpty
|
||||
? safeName[0].toUpperCase()
|
||||
: '?';
|
||||
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor:
|
||||
Colors.blueAccent.withOpacity(0.1),
|
||||
child: Text(initial,
|
||||
style: const TextStyle(
|
||||
color: Colors.blueAccent)),
|
||||
),
|
||||
title: Text(safeName),
|
||||
subtitle: Text(safePhone,
|
||||
textDirection: TextDirection.ltr),
|
||||
onTap: () {
|
||||
selectPhone(selectedPhone);
|
||||
},
|
||||
);
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
isScrollControlled: true,
|
||||
);
|
||||
} else {
|
||||
log('Permission DENIED', name: 'ContactPicker');
|
||||
mySnackeBarError('Contact permission is required to pick contacts'.tr);
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('An error occurred while picking contacts: $e'.tr);
|
||||
if (Get.isDialogOpen ?? false) Get.back();
|
||||
log('CRITICAL ERROR: $e', name: 'ContactPicker');
|
||||
mySnackeBarError('An error occurred while loading contacts: $e'.tr);
|
||||
}
|
||||
log('=== END: FETCHING CONTACTS ===', name: 'ContactPicker');
|
||||
}
|
||||
|
||||
/// **FIX**: Added the missing 'selectPhone' method.
|
||||
@@ -196,11 +348,11 @@ Download the Intaleq app now and enjoy your ride!
|
||||
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,
|
||||
));
|
||||
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') {
|
||||
@@ -210,23 +362,23 @@ Download the Intaleq app now and enjoy your ride!
|
||||
payload: {'id': (driverInvitationData[index]['id'])});
|
||||
await Get.find<CaptainWalletController>().addDriverPayment(
|
||||
'paymentMethod',
|
||||
('50000'),
|
||||
('500'),
|
||||
'',
|
||||
);
|
||||
await Get.find<CaptainWalletController>()
|
||||
.addDriverWalletToInvitor(
|
||||
'paymentMethod',
|
||||
(driverInvitationData[index]['driverInviterId']),
|
||||
('50000'),
|
||||
('500'),
|
||||
);
|
||||
NotificationCaptainController().addNotificationCaptain(
|
||||
driverInvitationData[index]['driverInviterId'].toString(),
|
||||
"You have got a gift for invitation".tr,
|
||||
'${"You have 50000".tr} ${'SYP'.tr}',
|
||||
'${"You have 500".tr} ${'SYP'.tr}',
|
||||
false);
|
||||
NotificationController().showNotification(
|
||||
"You have got a gift for invitation".tr,
|
||||
'${"You have 50000".tr} ${'SYP'.tr}',
|
||||
'${"You have 500".tr} ${'SYP'.tr}',
|
||||
'tone1',
|
||||
'');
|
||||
} else {
|
||||
@@ -265,21 +417,21 @@ Download the Intaleq app now and enjoy your ride!
|
||||
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,
|
||||
));
|
||||
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<CaptainWalletController>()
|
||||
.addDriverWallet('paymentMethod', '20000', '20000');
|
||||
.addDriverWallet('paymentMethod', '200', '200');
|
||||
await Get.find<CaptainWalletController>()
|
||||
.addDriverWalletToInvitor('paymentMethod',
|
||||
driverInvitationData[index]['driverInviterId'], '20000');
|
||||
driverInvitationData[index]['driverInviterId'], '200');
|
||||
await CRUD().post(
|
||||
link: AppLink.updatePassengerGift,
|
||||
payload: {'id': driverInvitationDataToPassengers[index]['id']},
|
||||
@@ -288,7 +440,7 @@ Download the Intaleq app now and enjoy your ride!
|
||||
driverInvitationDataToPassengers[index]['passengerInviterId']
|
||||
.toString(),
|
||||
"You have got a gift for invitation".tr,
|
||||
'${"You have 20000".tr} ${'SYP'.tr}',
|
||||
'${"You have 200".tr} ${'SYP'.tr}',
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user