feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9909d9b4c1
commit
4d8414c96b
@@ -0,0 +1,19 @@
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import 'crud.dart';
|
||||
|
||||
addError(String error, where) async {
|
||||
CRUD().post(link: AppLink.addError, payload: {
|
||||
'error': error.toString(), // Example error description
|
||||
'userId': box.read(BoxName.driverID) ??
|
||||
box.read(BoxName.passengerID), // Example user ID
|
||||
'userType': box.read(BoxName.driverID) != null
|
||||
? 'Driver'
|
||||
: 'passenger', // Example user type
|
||||
'phone': box.read(BoxName.phone) ??
|
||||
box.read(BoxName.phoneDriver), // Example phone number
|
||||
|
||||
'device': where
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import 'package:siro_rider/print.dart';
|
||||
import 'dart:io';
|
||||
|
||||
// import 'package:flutter_sound/flutter_sound.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:record/record.dart';
|
||||
|
||||
class AudioRecorderController extends GetxController {
|
||||
AudioPlayer audioPlayer = AudioPlayer();
|
||||
AudioRecorder recorder = AudioRecorder();
|
||||
|
||||
bool isRecording = false;
|
||||
bool isPlaying = false;
|
||||
bool isPaused = false;
|
||||
String filePath = '';
|
||||
String? selectedFilePath;
|
||||
double currentPosition = 0;
|
||||
double totalDuration = 0;
|
||||
|
||||
Future<void> playSoundFromAssets(String sound) async {
|
||||
try {
|
||||
await audioPlayer.setAsset(sound);
|
||||
audioPlayer.play();
|
||||
} catch (e) {
|
||||
Log.print("Error playing sound: $e");
|
||||
}
|
||||
}
|
||||
|
||||
// Start recording
|
||||
Future<void> startRecording({String? rideId}) async {
|
||||
final bool isPermissionGranted = await recorder.hasPermission();
|
||||
if (!isPermissionGranted) {
|
||||
// RecordingPermissionException('l');
|
||||
return;
|
||||
}
|
||||
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final String dateStr =
|
||||
'${DateTime.now().year}-${DateTime.now().month.toString().padLeft(2, '0')}-${DateTime.now().day.toString().padLeft(2, '0')}';
|
||||
// Generate a unique file name using the current timestamp
|
||||
String fileName = (rideId != null && rideId.isNotEmpty && rideId != 'yet' && rideId != 'null')
|
||||
? '${dateStr}_$rideId.m4a'
|
||||
: '$dateStr.m4a';
|
||||
filePath = '${directory.path}/$fileName';
|
||||
|
||||
// Define the configuration for the recording
|
||||
const config = RecordConfig(
|
||||
// Specify the format, encoder, sample rate, etc., as needed
|
||||
encoder: AudioEncoder.aacLc, // For example, using AAC codec
|
||||
sampleRate: 44100, // Sample rate
|
||||
bitRate: 128000, // Bit rate
|
||||
);
|
||||
|
||||
// Start recording to file with the specified configuration
|
||||
await recorder.start(config, path: filePath);
|
||||
|
||||
isRecording = true;
|
||||
update();
|
||||
}
|
||||
|
||||
// Pause recording
|
||||
Future<void> pauseRecording() async {
|
||||
if (isRecording && !isPaused) {
|
||||
await recorder.pause();
|
||||
isPaused = true;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// Resume recording
|
||||
Future<void> resumeRecording() async {
|
||||
if (isRecording && isPaused) {
|
||||
await recorder.resume();
|
||||
isPaused = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop recording
|
||||
Future<void> stopRecording() async {
|
||||
recorder.stop();
|
||||
isRecording = false;
|
||||
isPaused = false;
|
||||
update();
|
||||
}
|
||||
|
||||
// Play the selected recorded file
|
||||
Future<void> playRecordedFile(String filePath) async {
|
||||
await audioPlayer.setFilePath(filePath);
|
||||
totalDuration = audioPlayer.duration?.inSeconds.toDouble() ?? 0;
|
||||
audioPlayer.play();
|
||||
|
||||
isPlaying = true;
|
||||
isPaused = false;
|
||||
audioPlayer.positionStream.listen((position) {
|
||||
currentPosition = position.inSeconds.toDouble();
|
||||
update();
|
||||
});
|
||||
selectedFilePath = filePath;
|
||||
update();
|
||||
}
|
||||
|
||||
// Pause playback
|
||||
Future<void> pausePlayback() async {
|
||||
if (isPlaying && !isPaused) {
|
||||
await audioPlayer.pause();
|
||||
isPaused = true;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// Resume playback
|
||||
Future<void> resumePlayback() async {
|
||||
if (isPlaying && isPaused) {
|
||||
await audioPlayer.play();
|
||||
isPaused = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// Stop playback
|
||||
Future<void> stopPlayback() async {
|
||||
await audioPlayer.stop();
|
||||
isPlaying = false;
|
||||
isPaused = false;
|
||||
currentPosition = 0;
|
||||
update();
|
||||
}
|
||||
|
||||
// Get a list of recorded files
|
||||
Future<List<String>> getRecordedFiles() async {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final files = await directory.list().toList();
|
||||
return files
|
||||
.map((file) => file.path)
|
||||
.where((path) => path.endsWith('.m4a'))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Delete a specific recorded file
|
||||
Future<void> deleteRecordedFile(String filePath) async {
|
||||
final file = File(filePath);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all recorded files
|
||||
Future<void> deleteAllRecordedFiles() async {
|
||||
final directory = await getApplicationDocumentsDirectory();
|
||||
final files = await directory.list().toList();
|
||||
for (final file in files) {
|
||||
if (file.path.endsWith('.m4a')) {
|
||||
await deleteRecordedFile(file.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
audioPlayer.dispose();
|
||||
recorder.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// import 'package:SEFER/constant/api_key.dart';
|
||||
// import 'package:SEFER/controller/functions/crud.dart';
|
||||
// import 'package:SEFER/controller/home/map_passenger_controller.dart';
|
||||
// import 'package:agora_rtc_engine/agora_rtc_engine.dart';
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
// import '../../constant/box_name.dart';
|
||||
// import '../firebase/firbase_messge.dart';
|
||||
// import '../../main.dart';
|
||||
|
||||
// class CallController extends GetxController {
|
||||
// String channelName = ''; // Get.find<MapDriverController>().rideId;
|
||||
// String token = '';
|
||||
// // int uid = int.parse(box.read(BoxName.phoneDriver)); // uid of the local user
|
||||
// int uid = 0;
|
||||
// int? remoteUid; // uid of the remote user
|
||||
// bool _isJoined = false; // Indicates if the local user has joined the channel
|
||||
// String status = '';
|
||||
// late RtcEngine agoraEngine; // Agora engine instance
|
||||
|
||||
// @override
|
||||
// void onInit() {
|
||||
// super.onInit();
|
||||
// // if (box.read(BoxName.passengerID) != null) {
|
||||
// channelName = Get.find<MapPassengerController>().rideId; // 'sefer300'; //
|
||||
// remoteUid = int.parse(Get.find<MapPassengerController>().driverPhone);
|
||||
// uid = int.parse(box.read(BoxName.phone));
|
||||
// // } else {
|
||||
// // channelName = Get.find<MapDriverController>().rideId; // 'sefer300'; //
|
||||
// // remoteUid = int.parse(Get.find<MapDriverController>().passengerPhone);
|
||||
// // uid = int.parse(box.read(BoxName.phoneDriver));
|
||||
// // }
|
||||
|
||||
// initAgoraFull();
|
||||
// }
|
||||
|
||||
// initAgoraFull() async {
|
||||
// await fetchToken();
|
||||
// // Set up an instance of Agora engine
|
||||
// setupVoiceSDKEngine();
|
||||
// // join();
|
||||
// FirebaseMessagesController().sendNotificationToPassengerToken(
|
||||
// 'Call Income from Passenger',
|
||||
// '${'You have call from Passenger'.tr} ${box.read(BoxName.name)}',
|
||||
// Get.find<MapPassengerController>().driverToken,
|
||||
// [
|
||||
// token,
|
||||
// channelName,
|
||||
// uid.toString(),
|
||||
// remoteUid.toString(),
|
||||
// ],
|
||||
// 'iphone_ringtone.wav',
|
||||
// );
|
||||
// join();
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void onClose() {
|
||||
// agoraEngine.leaveChannel();
|
||||
// super.onClose();
|
||||
// }
|
||||
|
||||
// Future<void> setupVoiceSDKEngine() async {
|
||||
// // retrieve or request microphone permission
|
||||
// await [Permission.microphone].request();
|
||||
|
||||
// //create an instance of the Agora engine
|
||||
// agoraEngine = createAgoraRtcEngine();
|
||||
// await agoraEngine.initialize(RtcEngineContext(appId: AK.agoraAppId));
|
||||
// // Register the event handler
|
||||
// agoraEngine.registerEventHandler(
|
||||
// RtcEngineEventHandler(
|
||||
// onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
|
||||
// // Get.snackbar(
|
||||
// // "Local user uid:${connection.localUid} joined the channel", '');
|
||||
// status = 'joined'.tr;
|
||||
// _isJoined = true;
|
||||
// update();
|
||||
// },
|
||||
// onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
|
||||
// // Get.snackbar("Remote user uid:$remoteUid joined the channel", '');
|
||||
// status = Get.find<MapPassengerController>().driverName.toString();
|
||||
// ' joined'.tr;
|
||||
// remoteUid = remoteUid;
|
||||
// update();
|
||||
// },
|
||||
// onUserOffline: (RtcConnection connection, int? remoteUid,
|
||||
// UserOfflineReasonType reason) {
|
||||
// // Get.snackbar("Remote user uid:$remoteUid left the channel", '');
|
||||
// status = 'Call left'.tr;
|
||||
// remoteUid = null;
|
||||
// update();
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// void join() async {
|
||||
// // Set channel options including the client role and channel profile
|
||||
// ChannelMediaOptions options = const ChannelMediaOptions(
|
||||
// clientRoleType: ClientRoleType.clientRoleBroadcaster,
|
||||
// channelProfile: ChannelProfileType.channelProfileCommunication,
|
||||
// );
|
||||
|
||||
// await agoraEngine.joinChannel(
|
||||
// token: token,
|
||||
// channelId: channelName,
|
||||
// options: options,
|
||||
// uid: uid,
|
||||
// );
|
||||
// }
|
||||
|
||||
// void leave() {
|
||||
// _isJoined = false;
|
||||
// remoteUid = null;
|
||||
// update();
|
||||
// agoraEngine.leaveChannel();
|
||||
// }
|
||||
|
||||
// // Clean up the resources when you leave
|
||||
// @override
|
||||
// void dispose() async {
|
||||
// await agoraEngine.leaveChannel();
|
||||
// super.dispose();
|
||||
// }
|
||||
|
||||
// fetchToken() async {
|
||||
// var res = await CRUD()
|
||||
// .getAgoraToken(channelName: channelName, uid: uid.toString());
|
||||
// token = res;
|
||||
// update();
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,204 @@
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:intaleq_maps/intaleq_maps.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/country_polygons.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import '../../print.dart';
|
||||
|
||||
class CountryLogic {
|
||||
/// Formats the phone number based on the country's dialing rules.
|
||||
static String formatPhone(String phone, String country) {
|
||||
phone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
|
||||
|
||||
if (country == 'Egypt') {
|
||||
// Rule: 010, 011, 012, 015 -> 2010, 2011, 2012, 2015
|
||||
if (phone.startsWith('0020')) phone = phone.replaceFirst('0020', '20');
|
||||
if (phone.startsWith('01') && phone.length >= 10) {
|
||||
return '20${phone.substring(1)}';
|
||||
}
|
||||
if (phone.startsWith('1') &&
|
||||
phone.length >= 9 &&
|
||||
!phone.startsWith('20')) {
|
||||
return '20$phone';
|
||||
}
|
||||
if (!phone.startsWith('20') && phone.length > 8) return '20$phone';
|
||||
} else if (country == 'Jordan') {
|
||||
// Rule: 07x -> 9627x
|
||||
if (phone.startsWith('00962')) phone = phone.replaceFirst('00962', '962');
|
||||
if (phone.startsWith('07') && phone.length >= 9) {
|
||||
return '962${phone.substring(1)}';
|
||||
}
|
||||
if (phone.startsWith('7') &&
|
||||
phone.length >= 8 &&
|
||||
!phone.startsWith('962')) {
|
||||
return '962$phone';
|
||||
}
|
||||
if (!phone.startsWith('962') && phone.length > 7) return '962$phone';
|
||||
} else {
|
||||
// Default to Syria
|
||||
if (phone.startsWith('00963')) phone = phone.replaceFirst('00963', '963');
|
||||
if (phone.startsWith('0963')) phone = phone.replaceFirst('0963', '963');
|
||||
if (phone.startsWith('096309')) {
|
||||
phone = phone.replaceFirst('096309', '963');
|
||||
}
|
||||
if (phone.startsWith('96309')) phone = '9639${phone.substring(5)}';
|
||||
if (phone.startsWith('9630')) phone = '9639${phone.substring(4)}';
|
||||
if (phone.startsWith('9639') && phone.length == 12) return phone;
|
||||
if (phone.startsWith('963') &&
|
||||
phone.length > 3 &&
|
||||
!phone.startsWith('9639')) {
|
||||
phone = '9639${phone.substring(3)}';
|
||||
}
|
||||
if (phone.startsWith('09')) return '963${phone.substring(1)}';
|
||||
if (phone.startsWith('9') && phone.length == 9) return '963$phone';
|
||||
if (phone.startsWith('0') && phone.length == 10) {
|
||||
return '963${phone.substring(1)}';
|
||||
}
|
||||
}
|
||||
return phone;
|
||||
}
|
||||
|
||||
/// Returns the default country prefix (EG, JO, SY) for UI initial selection.
|
||||
static String getCountryPrefix(String country) {
|
||||
if (country == 'Egypt') return 'EG';
|
||||
if (country == 'Jordan') return 'JO';
|
||||
return 'SY';
|
||||
}
|
||||
|
||||
/// Returns the default emergency number for the country.
|
||||
static String getEmergencyNumber(String country) {
|
||||
if (country == 'Egypt') return '122';
|
||||
if (country == 'Jordan') return '911';
|
||||
return '112'; // Syria
|
||||
}
|
||||
|
||||
/// Returns the hint text for phone inputs based on the country.
|
||||
static String getPhoneHint(String country) {
|
||||
if (country == 'Egypt') return 'e.g. 01012345678 (Default +20)';
|
||||
if (country == 'Jordan') return 'e.g. 0791234567 (Default +962)';
|
||||
return 'e.g. 0912345678 (Default +963)'; // Syria
|
||||
}
|
||||
|
||||
/// Helper to format phone using the current country in box.
|
||||
static String formatCurrentCountryPhone(String phone) {
|
||||
String cleanPhone = phone.replaceAll(RegExp(r'[ \-\(\)\+]'), '').trim();
|
||||
|
||||
// 1. Explicit International Code Detection
|
||||
if (cleanPhone.startsWith('00963')) {
|
||||
cleanPhone = cleanPhone.replaceFirst('00963', '963');
|
||||
}
|
||||
if (cleanPhone.startsWith('00962')) {
|
||||
cleanPhone = cleanPhone.replaceFirst('00962', '962');
|
||||
}
|
||||
if (cleanPhone.startsWith('0020')) {
|
||||
cleanPhone = cleanPhone.replaceFirst('0020', '20');
|
||||
}
|
||||
|
||||
if (cleanPhone.startsWith('963')) {
|
||||
return formatPhone(cleanPhone, 'Syria');
|
||||
}
|
||||
if (cleanPhone.startsWith('962')) {
|
||||
return formatPhone(cleanPhone, 'Jordan');
|
||||
}
|
||||
if (cleanPhone.startsWith('20')) {
|
||||
return formatPhone(cleanPhone, 'Egypt');
|
||||
}
|
||||
|
||||
// 2. Local/National Format Detection by Country-Specific Mobile Prefixes
|
||||
// Jordan: 07x / 7x (9 national digits)
|
||||
if (cleanPhone.startsWith('07') && cleanPhone.length == 10) {
|
||||
return formatPhone(cleanPhone, 'Jordan');
|
||||
}
|
||||
if (cleanPhone.startsWith('7') && cleanPhone.length == 9) {
|
||||
return formatPhone(cleanPhone, 'Jordan');
|
||||
}
|
||||
|
||||
// Syria: 09x / 9x (9 national digits)
|
||||
if (cleanPhone.startsWith('09') && cleanPhone.length == 10) {
|
||||
return formatPhone(cleanPhone, 'Syria');
|
||||
}
|
||||
if (cleanPhone.startsWith('9') && cleanPhone.length == 9) {
|
||||
return formatPhone(cleanPhone, 'Syria');
|
||||
}
|
||||
|
||||
// Egypt: 01x (10 national digits) / 1x (9 national digits)
|
||||
if (cleanPhone.startsWith('01') && cleanPhone.length == 11) {
|
||||
return formatPhone(cleanPhone, 'Egypt');
|
||||
}
|
||||
if (cleanPhone.startsWith('1') && cleanPhone.length == 10) {
|
||||
return formatPhone(cleanPhone, 'Egypt');
|
||||
}
|
||||
|
||||
// 3. Fallback: Default to current user's country code saved in box
|
||||
final country = box.read(BoxName.countryCode) ?? 'Jordan';
|
||||
return formatPhone(cleanPhone, country);
|
||||
}
|
||||
|
||||
/// التحقق مما إذا كانت نقطة داخل مضلع (Ray Casting Algorithm)
|
||||
static bool _isPointInPolygon(LatLng point, List<LatLng> polygon) {
|
||||
int intersections = 0;
|
||||
for (int i = 0; i < polygon.length; i++) {
|
||||
final v1 = polygon[i];
|
||||
final v2 = polygon[(i + 1) % polygon.length];
|
||||
if ((v1.latitude <= point.latitude && v2.latitude > point.latitude) ||
|
||||
(v2.latitude <= point.latitude && v1.latitude > point.latitude)) {
|
||||
final t = (point.latitude - v1.latitude) / (v2.latitude - v1.latitude);
|
||||
if (point.longitude <
|
||||
v1.longitude + t * (v2.longitude - v1.longitude)) {
|
||||
intersections++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return intersections % 2 != 0;
|
||||
}
|
||||
|
||||
/// كشف الدولة تلقائياً عبر الموقع الجغرافي باستخدام مضلعات CountryPolygons
|
||||
static Future<String?> detectByLocation() async {
|
||||
try {
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) return null;
|
||||
|
||||
LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final pos = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.low,
|
||||
timeLimit: const Duration(seconds: 8),
|
||||
);
|
||||
final point = LatLng(pos.latitude, pos.longitude);
|
||||
|
||||
if (_isPointInPolygon(point, CountryPolygons.jordanBoundary)) {
|
||||
return 'Jordan';
|
||||
}
|
||||
if (_isPointInPolygon(point, CountryPolygons.syriaBoundary)) {
|
||||
return 'Syria';
|
||||
}
|
||||
if (_isPointInPolygon(point, CountryPolygons.egyptBoundary)) {
|
||||
return 'Egypt';
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('[CountryLogic] detectByLocation error: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// تهيئة الدولة عند بدء التطبيق
|
||||
static Future<void> initializeCountry() async {
|
||||
final existing = box.read(BoxName.countryCode);
|
||||
if (existing != null && existing.toString().isNotEmpty) {
|
||||
Log.print('[CountryLogic] الدولة موجودة مسبقاً: $existing');
|
||||
return;
|
||||
}
|
||||
Log.print('[CountryLogic] كشف الدولة عبر الموقع...');
|
||||
final country = await detectByLocation();
|
||||
if (country != null) {
|
||||
box.write(BoxName.countryCode, country);
|
||||
Log.print('[CountryLogic] تم الكشف: $country');
|
||||
} else {
|
||||
box.write(BoxName.countryCode, 'Jordan');
|
||||
Log.print('[CountryLogic] افتراضي: Jordan');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,625 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:siro_rider/env/env.dart';
|
||||
|
||||
import '../../constant/api_key.dart';
|
||||
|
||||
import '../../print.dart';
|
||||
import '../../views/widgets/elevated_btn.dart';
|
||||
import '../../views/widgets/error_snakbar.dart';
|
||||
import 'encrypt_decrypt.dart';
|
||||
import 'upload_image.dart';
|
||||
import 'dart:io';
|
||||
|
||||
import 'network/net_guard.dart';
|
||||
import 'ssl_pinning.dart';
|
||||
|
||||
class CRUD {
|
||||
final NetGuard _netGuard = NetGuard();
|
||||
final _client = SslPinning.createPinnedClient();
|
||||
|
||||
static bool _isRefreshingJWT = false;
|
||||
static String _lastErrorSignature = '';
|
||||
static DateTime _lastErrorTimestamp = DateTime(2000);
|
||||
static const Duration _errorLogDebounceDuration = Duration(minutes: 1);
|
||||
|
||||
/// JWT validity check without external libraries.
|
||||
static bool _isJwtValid(String? token) {
|
||||
if (token == null || token.isEmpty) return false;
|
||||
try {
|
||||
final parts = token.split('.');
|
||||
if (parts.length != 3) return false;
|
||||
String payload = parts[1];
|
||||
switch (payload.length % 4) {
|
||||
case 2:
|
||||
payload += '==';
|
||||
break;
|
||||
case 3:
|
||||
payload += '=';
|
||||
break;
|
||||
}
|
||||
final decoded = jsonDecode(utf8.decode(base64Url.decode(payload)));
|
||||
final exp = decoded['exp'];
|
||||
if (exp == null) return false;
|
||||
return DateTime.now().millisecondsSinceEpoch < (exp * 1000 - 30000);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> addError(
|
||||
String error, String details, String where) async {
|
||||
try {
|
||||
final currentErrorSignature = '$where-$error';
|
||||
final now = DateTime.now();
|
||||
|
||||
if (currentErrorSignature == _lastErrorSignature &&
|
||||
now.difference(_lastErrorTimestamp) < _errorLogDebounceDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastErrorSignature = currentErrorSignature;
|
||||
_lastErrorTimestamp = now;
|
||||
|
||||
final userId =
|
||||
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID);
|
||||
final userType =
|
||||
box.read(BoxName.driverID) != null ? 'Driver' : 'Passenger';
|
||||
final phone = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
||||
|
||||
Log.print(
|
||||
"🚨 [ADD_ERROR] Where: $where | Error: $error | Details: $details");
|
||||
|
||||
CRUD().post(
|
||||
link: AppLink.addError,
|
||||
payload: {
|
||||
'error': error.toString(),
|
||||
'userId': userId.toString(),
|
||||
'userType': userType,
|
||||
'phone': phone.toString(),
|
||||
'device': where,
|
||||
'details': details,
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
Log.print("Error occurred: $e");
|
||||
}
|
||||
}
|
||||
|
||||
String _getFpHeader() {
|
||||
return box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
||||
}
|
||||
|
||||
Future<String> _getJwt() async {
|
||||
try {
|
||||
final String? encryptedJwt = await storage.read(key: BoxName.jwt);
|
||||
if (encryptedJwt == null || encryptedJwt.isEmpty) {
|
||||
final String? fallback = box.read(BoxName.jwt);
|
||||
if (fallback != null) {
|
||||
return r(fallback).toString().split(Env.addd)[0];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
return r(encryptedJwt).toString().split(Env.addd)[0];
|
||||
} catch (e) {
|
||||
Log.print('Error reading JWT from SecureStorage: $e');
|
||||
final String? fallback = box.read(BoxName.jwt);
|
||||
if (fallback != null) {
|
||||
return r(fallback).toString().split(Env.addd)[0];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// Centralized request handler with retry for weak networks.
|
||||
/// For Syria (3G): 60s total timeout, 3 retries, exponential backoff.
|
||||
Future<dynamic> _makeRequest({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
const totalTimeout = Duration(seconds: 60);
|
||||
|
||||
Future<http.Response> doPost() {
|
||||
final url = Uri.parse(link);
|
||||
return _client
|
||||
.post(url, body: payload, headers: headers)
|
||||
.timeout(totalTimeout);
|
||||
}
|
||||
|
||||
http.Response? response;
|
||||
int attempts = 0;
|
||||
|
||||
while (attempts < 3) {
|
||||
try {
|
||||
attempts++;
|
||||
response = await doPost();
|
||||
break;
|
||||
} on SocketException catch (_) {
|
||||
Log.print('⚠️ SocketException attempt $attempts — $link');
|
||||
if (attempts >= 3) {
|
||||
_netGuard.notifyOnce((title, msg) => mySnackbarError(msg));
|
||||
return 'no_internet';
|
||||
}
|
||||
await Future.delayed(Duration(seconds: attempts));
|
||||
} on TimeoutException catch (_) {
|
||||
Log.print('⚠️ TimeoutException attempt $attempts — $link');
|
||||
if (attempts >= 3) return 'failure';
|
||||
} catch (e) {
|
||||
if (e.toString().contains('errno = 9') && attempts < 3) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
continue;
|
||||
}
|
||||
addError(
|
||||
'HTTP Exception: $e', 'Try: $attempts', 'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
}
|
||||
|
||||
if (response == null) return 'failure';
|
||||
|
||||
final sc = response.statusCode;
|
||||
final body = response.body;
|
||||
Log.print('request: ${response.request}');
|
||||
Log.print('body: $body');
|
||||
Log.print('payload: $payload');
|
||||
|
||||
if (sc >= 200 && sc < 300) {
|
||||
try {
|
||||
return jsonDecode(body);
|
||||
} catch (e, st) {
|
||||
addError('JSON Decode Error', 'Body: $body\n$st',
|
||||
'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
}
|
||||
|
||||
if (sc == 401) {
|
||||
final isNonCritical = link.contains('errorApp.php');
|
||||
if (!_isRefreshingJWT && !isNonCritical) {
|
||||
_isRefreshingJWT = true;
|
||||
try {
|
||||
await Get.put(LoginController()).getJWT();
|
||||
} finally {
|
||||
_isRefreshingJWT = false;
|
||||
}
|
||||
}
|
||||
return 'token_expired';
|
||||
}
|
||||
|
||||
if (sc >= 500) {
|
||||
addError(
|
||||
'Server 5xx', 'SC: $sc\nBody: $body', 'CRUD._makeRequest $link');
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
return 'failure';
|
||||
}
|
||||
|
||||
Future<dynamic> post({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
String token = await _getJwt();
|
||||
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $token',
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
|
||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
}
|
||||
|
||||
Future<dynamic> get({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
String token = await _getJwt();
|
||||
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $token',
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
|
||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// postWallet — طلب POST لسيرفر المدفوعات
|
||||
// ───────────────────────────────────────────────────────────────
|
||||
// التغيير: إضافة X-Device-FP header
|
||||
// 3 headers معاً: JWT + HMAC + FP
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
Future<dynamic> postWallet({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
var jwt = await LoginController().getJwtWallet();
|
||||
final hmac = box.read(BoxName.hmac);
|
||||
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $jwt',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
Log.print('headers: $headers');
|
||||
Log.print('payload: $payload');
|
||||
Log.print('link: $link');
|
||||
|
||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
}
|
||||
|
||||
Future<dynamic> getWallet({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
var s = await LoginController().getJwtWallet();
|
||||
final hmac = box.read(BoxName.hmac);
|
||||
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $s',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
|
||||
return await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
}
|
||||
|
||||
// =======================================================================
|
||||
// All other specialized methods remain below unchanged.
|
||||
// They interact with external third-party APIs and have unique
|
||||
// authentication or body structures that don't need the FP header.
|
||||
// =======================================================================
|
||||
|
||||
Future<dynamic> postWalletMtn(
|
||||
{required String link, Map<String, dynamic>? payload}) async {
|
||||
final s = await LoginController().getJwtWallet();
|
||||
final hmac = box.read(BoxName.hmac);
|
||||
|
||||
final headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Bearer $s',
|
||||
'X-HMAC-Auth': hmac.toString(),
|
||||
'X-Device-FP': _getFpHeader(),
|
||||
};
|
||||
|
||||
final result = await _makeRequest(link: link, payload: payload, headers: headers);
|
||||
if (result is Map || result is List) return result;
|
||||
if (result == 'no_internet') {
|
||||
return {'status': 'failure', 'message': 'no_internet', 'code': -1};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future sendWhatsAppAuth(String to, String token) async {
|
||||
var res = await CRUD()
|
||||
.get(link: AppLink.getApiKey, payload: {'keyName': 'whatsapp_key'});
|
||||
if (res is! Map) return;
|
||||
var accesstoken = res['message']['whatsapp_key'];
|
||||
var headers = {
|
||||
'Authorization': 'Bearer $accesstoken',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
var url = 'https://graph.facebook.com/v20.0/${Env.whatappID}/messages';
|
||||
var request = http.Request('POST', Uri.parse(url));
|
||||
|
||||
var body = json.encode({
|
||||
"messaging_product": "whatsapp",
|
||||
"to": to,
|
||||
"type": "template",
|
||||
"template": {
|
||||
"name": "sefer1",
|
||||
"language": {"code": "en"},
|
||||
"components": [
|
||||
{
|
||||
"type": "body",
|
||||
"parameters": [
|
||||
{"type": "text", "text": token}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
request.body = body;
|
||||
request.headers.addAll(headers);
|
||||
|
||||
try {
|
||||
http.StreamedResponse response = await request.send();
|
||||
if (response.statusCode == 200) {
|
||||
String responseBody = await response.stream.bytesToString();
|
||||
Get.defaultDialog(
|
||||
title: 'You will receive a code in WhatsApp Messenger'.tr,
|
||||
middleText: 'wait 1 minute to recive message'.tr,
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
String errorBody = await response.stream.bytesToString();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print("Error occurred: $e");
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> getAgoraToken({
|
||||
required String channelName,
|
||||
required String uid,
|
||||
}) async {
|
||||
var uid = box.read(BoxName.phone) ?? box.read(BoxName.phoneDriver);
|
||||
var res = await _client.get(
|
||||
Uri.parse(
|
||||
'https://orca-app-b2i85.ondigitalocean.app/token?channelName=$channelName'),
|
||||
headers: {'Authorization': 'Bearer ${AK.agoraAppCertificate}'},
|
||||
);
|
||||
|
||||
if (res.statusCode == 200) {
|
||||
var response = jsonDecode(res.body);
|
||||
return response['token'];
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> getLlama({
|
||||
required String link,
|
||||
required String payload,
|
||||
required String prompt,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
var headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization':
|
||||
'Bearer LL-X5lJ0Px9CzKK0HTuVZ3u2u4v3tGWkImLTG7okGRk4t25zrsLqJ0qNoUzZ2x4ciPy'
|
||||
};
|
||||
var data = json.encode({
|
||||
"model": "Llama-3-70b-Inst-FW",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content":
|
||||
"Extract the desired information from the following passage as json decoded like $prompt just in this:\n\n$payload"
|
||||
}
|
||||
],
|
||||
"temperature": 0.9
|
||||
});
|
||||
var response = await _client.post(url, body: data, headers: headers);
|
||||
if (response.statusCode == 200) return response.body;
|
||||
return response.statusCode;
|
||||
}
|
||||
|
||||
Future allMethodForAI(String prompt, linkPHP, imagePath) async {
|
||||
await ImageController().choosImage(linkPHP, imagePath);
|
||||
Future.delayed(const Duration(seconds: 2));
|
||||
String extracted =
|
||||
await arabicTextExtractByVisionAndAI(imagePath: imagePath);
|
||||
}
|
||||
|
||||
Future<dynamic> arabicTextExtractByVisionAndAI({
|
||||
required String imagePath,
|
||||
}) async {
|
||||
var headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Ocp-Apim-Subscription-Key': '21010e54b50f41a4904708c526e102df'
|
||||
};
|
||||
var url = Uri.parse(
|
||||
'https://ocrhamza.cognitiveservices.azure.com/vision/v2.1/ocr?language=ar');
|
||||
String imagePathFull =
|
||||
'${AppLink.server}card_image/$imagePath-${box.read(BoxName.driverID) ?? box.read(BoxName.passengerID)}.jpg';
|
||||
|
||||
var requestBody = {"url": imagePathFull};
|
||||
var response =
|
||||
await _client.post(url, body: jsonEncode(requestBody), headers: headers);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
var responseBody = jsonDecode(response.body);
|
||||
return responseBody.toString();
|
||||
}
|
||||
return response.statusCode;
|
||||
}
|
||||
|
||||
Future<dynamic> getChatGPT({
|
||||
required String link,
|
||||
required String payload,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
var headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ${Env.chatGPTkeySeferNew}'
|
||||
};
|
||||
var data = json.encode({
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content":
|
||||
"Extract the desired information from the following passage as json decoded like vin,make,made,year,expiration_date,color,owner,registration_date just in this:\n\n$payload"
|
||||
}
|
||||
],
|
||||
"temperature": 0.9
|
||||
});
|
||||
var response = await _client.post(url, body: data, headers: headers);
|
||||
if (response.statusCode == 200) return response.body;
|
||||
return response.statusCode;
|
||||
}
|
||||
|
||||
Future<dynamic> postPayMob({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
var response = await _client.post(url,
|
||||
body: payload, headers: {'Content-Type': 'application/json'});
|
||||
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (response.statusCode == 200) {
|
||||
if (jsonData['status'] == 'success') return response.body;
|
||||
return jsonData['status'];
|
||||
} else {
|
||||
return response.statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
sendEmail(String link, Map<String, String>? payload) async {
|
||||
var headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization':
|
||||
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
|
||||
};
|
||||
var request = http.Request('POST', Uri.parse(link));
|
||||
request.bodyFields = payload!;
|
||||
request.headers.addAll(headers);
|
||||
await request.send();
|
||||
}
|
||||
|
||||
Future<dynamic> postFromDialogue({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
var response = await _client.post(
|
||||
url,
|
||||
body: payload,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization':
|
||||
'Basic ${base64Encode(utf8.encode(AK.basicAuthCredentials))}',
|
||||
},
|
||||
);
|
||||
|
||||
if (response.body.isNotEmpty) {
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (response.statusCode == 200) {
|
||||
if (jsonData['status'] == 'success') {
|
||||
Get.back();
|
||||
return response.body;
|
||||
}
|
||||
}
|
||||
return jsonData['status'];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> sendVerificationRequest(String phoneNumber) async {
|
||||
final accountSid = AK.accountSIDTwillo;
|
||||
final authToken = AK.authTokenTwillo;
|
||||
final verifySid = AK.twilloRecoveryCode;
|
||||
|
||||
final Uri verificationUri = Uri.parse(
|
||||
'https://verify.twilio.com/v2/Services/$verifySid/Verifications');
|
||||
|
||||
await _client.post(
|
||||
verificationUri,
|
||||
headers: {
|
||||
'Authorization':
|
||||
'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {'To': phoneNumber, 'Channel': 'sms'},
|
||||
);
|
||||
|
||||
final otpCode = "123456";
|
||||
|
||||
final checkUri = Uri.parse(
|
||||
'https://verify.twilio.com/v2/Services/$verifySid/VerificationCheck');
|
||||
|
||||
final checkResponse = await _client.post(
|
||||
checkUri,
|
||||
headers: {
|
||||
'Authorization':
|
||||
'Basic ' + base64Encode(utf8.encode('$accountSid:$authToken')),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {'To': phoneNumber, 'Code': otpCode},
|
||||
);
|
||||
}
|
||||
|
||||
Future<dynamic> getGoogleApi({
|
||||
required String link,
|
||||
Map<String, dynamic>? payload,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
var response = await _client.post(url, body: payload);
|
||||
var jsonData = jsonDecode(response.body);
|
||||
if (jsonData['status'] == 'OK') return jsonData;
|
||||
return jsonData['status'];
|
||||
}
|
||||
|
||||
Future<dynamic> getHereMap({required String link}) async {
|
||||
var url = Uri.parse(link);
|
||||
try {
|
||||
var response = await _client.get(url);
|
||||
if (response.statusCode == 200) {
|
||||
var decodedBody = utf8.decode(response.bodyBytes);
|
||||
return jsonDecode(decodedBody);
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> getMapSaas({
|
||||
required String link,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
try {
|
||||
var response = await _client.get(
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': Env.mapSaasKey,
|
||||
},
|
||||
);
|
||||
Log.print('link -MapSaas: $link');
|
||||
Log.print('response -MapSaas: ${response.body}');
|
||||
if (response.statusCode == 200) {
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
Log.print('MapSaas Error: ${response.statusCode} - ${response.body}');
|
||||
return null;
|
||||
} catch (e) {
|
||||
Log.print('MapSaas Exception: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> postMapSaas({
|
||||
required String link,
|
||||
required Map<String, dynamic> payload,
|
||||
}) async {
|
||||
var url = Uri.parse(link);
|
||||
try {
|
||||
var response = await _client.post(
|
||||
url,
|
||||
body: jsonEncode(payload),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': Env.mapSaasKey,
|
||||
},
|
||||
);
|
||||
Log.print('post -MapSaas link: $link');
|
||||
Log.print('post -MapSaas payload: $payload');
|
||||
Log.print('post -MapSaas response: ${response.body}');
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
return jsonDecode(response.body);
|
||||
}
|
||||
Log.print(
|
||||
'MapSaas Post Error: ${response.statusCode} - ${response.body}');
|
||||
return null;
|
||||
} catch (e) {
|
||||
Log.print('MapSaas Post Exception: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LineChartPainter extends CustomPainter {
|
||||
final List<double> data;
|
||||
|
||||
LineChartPainter(this.data);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
// Calculate the scale factor.
|
||||
final scaleFactor = size.height / 240;
|
||||
|
||||
// Draw the line chart.
|
||||
for (var i = 0; i < data.length - 1; i++) {
|
||||
final x1 = i * size.width / data.length;
|
||||
final y1 = data[i] * scaleFactor;
|
||||
final x2 = (i + 1) * size.width / data.length;
|
||||
final y2 = data[i + 1] * scaleFactor;
|
||||
|
||||
canvas.drawLine(Offset(x1, y1), Offset(x2, y2), Paint());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(LineChartPainter oldDelegate) => false;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
import '../../print.dart';
|
||||
|
||||
class DeviceInfoPlus {
|
||||
static List<Map<String, dynamic>> deviceDataList = [];
|
||||
|
||||
static Future<List<Map<String, dynamic>>> getDeviceInfo() async {
|
||||
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
|
||||
try {
|
||||
if (Platform.isAndroid) {
|
||||
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
Map<String, dynamic> deviceData = {
|
||||
'platform': 'Android',
|
||||
'brand': androidInfo.brand,
|
||||
'model': androidInfo.model,
|
||||
'androidId': androidInfo.device,
|
||||
'versionRelease': androidInfo.version.release,
|
||||
'sdkVersion': androidInfo.version.sdkInt,
|
||||
'manufacturer': androidInfo.manufacturer,
|
||||
'isPhysicalDevice': androidInfo.isPhysicalDevice,
|
||||
'serialNumber': androidInfo.fingerprint,
|
||||
'fingerprint': androidInfo.fingerprint,
|
||||
'type': androidInfo.type,
|
||||
'data': androidInfo.data,
|
||||
'version': androidInfo.version,
|
||||
'tags': androidInfo.tags,
|
||||
'display': androidInfo.display,
|
||||
};
|
||||
// Log.print('deviceData: ${deviceData}');
|
||||
deviceDataList.add(deviceData);
|
||||
} else if (Platform.isIOS) {
|
||||
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
|
||||
Map<String, dynamic> deviceData = {
|
||||
'brand': 'Apple',
|
||||
'model': iosInfo.model,
|
||||
'systemName': iosInfo.systemName,
|
||||
'systemVersion': iosInfo.systemVersion,
|
||||
'utsname': iosInfo.utsname,
|
||||
'isPhysicalDevice': iosInfo.isPhysicalDevice,
|
||||
'identifierForVendor': iosInfo.identifierForVendor,
|
||||
'name': iosInfo.name,
|
||||
'localizedModel': iosInfo.localizedModel,
|
||||
};
|
||||
deviceDataList.add(deviceData);
|
||||
} else if (Platform.isMacOS) {
|
||||
MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo;
|
||||
Map<String, dynamic> deviceData = {
|
||||
'platform': 'macOS',
|
||||
'model': macInfo.model,
|
||||
'version': macInfo.systemGUID,
|
||||
};
|
||||
deviceDataList.add(deviceData);
|
||||
} else if (Platform.isWindows) {
|
||||
WindowsDeviceInfo windowsInfo = await deviceInfoPlugin.windowsInfo;
|
||||
Map<String, dynamic> deviceData = {
|
||||
'platform': 'Windows',
|
||||
'manufacturer': windowsInfo.computerName,
|
||||
'version': windowsInfo.majorVersion,
|
||||
'deviceId': windowsInfo.deviceId,
|
||||
'userName': windowsInfo.userName,
|
||||
'productName': windowsInfo.productName,
|
||||
'installDate': windowsInfo.installDate,
|
||||
'productId': windowsInfo.productId,
|
||||
'numberOfCores': windowsInfo.numberOfCores,
|
||||
'systemMemoryInMegabytes': windowsInfo.systemMemoryInMegabytes,
|
||||
};
|
||||
deviceDataList.add(deviceData);
|
||||
} else if (Platform.isLinux) {
|
||||
LinuxDeviceInfo linuxInfo = await deviceInfoPlugin.linuxInfo;
|
||||
Map<String, dynamic> deviceData = {
|
||||
'platform': 'Linux',
|
||||
'manufacturer': linuxInfo.name,
|
||||
'version': linuxInfo.version,
|
||||
};
|
||||
deviceDataList.add(deviceData);
|
||||
}
|
||||
} catch (e) { Log.print("Error occurred: $e"); }
|
||||
|
||||
return deviceDataList;
|
||||
}
|
||||
|
||||
// Method to print all device data
|
||||
static void printDeviceInfo() {
|
||||
for (Map<String, dynamic> deviceData in deviceDataList) {
|
||||
'Version: ${deviceData['version'] ?? deviceData['versionRelease'] ?? 'N/A'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// import 'package:flutter/services.dart';
|
||||
|
||||
// class DigitObscuringFormatter extends TextInputFormatter {
|
||||
// @override
|
||||
// TextEditingValue formatEditUpdate(
|
||||
// TextEditingValue oldValue, TextEditingValue newValue) {
|
||||
// final maskedText = maskDigits(newValue.text);
|
||||
// return newValue.copyWith(
|
||||
// text: maskedText,
|
||||
// selection: updateCursorPosition(maskedText, newValue.selection));
|
||||
// }
|
||||
|
||||
// String maskDigits(String text) {
|
||||
// final totalDigits = text.length;
|
||||
// final visibleDigits = 4;
|
||||
// final hiddenDigits = totalDigits - visibleDigits * 2;
|
||||
|
||||
// final firstVisibleDigits = text.substring(0, visibleDigits);
|
||||
// final lastVisibleDigits = text.substring(totalDigits - visibleDigits);
|
||||
|
||||
// final maskedDigits = List.filled(hiddenDigits, '*').join();
|
||||
|
||||
// return '$firstVisibleDigits$maskedDigits$lastVisibleDigits';
|
||||
// }
|
||||
|
||||
// TextSelection updateCursorPosition(
|
||||
// String maskedText, TextSelection currentSelection) {
|
||||
// final cursorPosition = currentSelection.baseOffset;
|
||||
// final cursorOffset =
|
||||
// currentSelection.extentOffset - currentSelection.baseOffset;
|
||||
// final totalDigits = maskedText.length;
|
||||
// const visibleDigits = 4;
|
||||
// final hiddenDigits = totalDigits - visibleDigits * 2;
|
||||
|
||||
// final updatedPosition = cursorPosition <= visibleDigits
|
||||
// ? cursorPosition
|
||||
// : hiddenDigits + visibleDigits + (cursorPosition - visibleDigits);
|
||||
|
||||
// return TextSelection.collapsed(
|
||||
// offset: updatedPosition, affinity: currentSelection.affinity);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,123 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:siro_rider/env/env.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
||||
|
||||
import '../../constant/char_map.dart';
|
||||
|
||||
class EncryptionHelper {
|
||||
static EncryptionHelper? _instance;
|
||||
|
||||
late final encrypt.Key key;
|
||||
late final encrypt.IV iv;
|
||||
static const String _gcmPrefix = 'GCM:';
|
||||
|
||||
EncryptionHelper._(this.key, this.iv);
|
||||
static EncryptionHelper get instance {
|
||||
if (_instance == null) {
|
||||
throw Exception(
|
||||
"EncryptionHelper is not initialized. Call `await EncryptionHelper.initialize()` in main.");
|
||||
}
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
/// Initializes and stores the instance globally
|
||||
static Future<void> initialize() async {
|
||||
if (_instance != null) {
|
||||
debugPrint("EncryptionHelper is already initialized.");
|
||||
return; // Prevent re-initialization
|
||||
}
|
||||
debugPrint("Initializing EncryptionHelper...");
|
||||
var keyOfApp = r(Env.keyOfApp).toString().split(Env.addd)[0];
|
||||
var initializationVector =
|
||||
r(Env.initializationVector).toString().split(Env.addd)[0];
|
||||
|
||||
// Set the global instance
|
||||
_instance = EncryptionHelper._(
|
||||
encrypt.Key.fromUtf8(keyOfApp!),
|
||||
encrypt.IV.fromUtf8(initializationVector!),
|
||||
);
|
||||
debugPrint("EncryptionHelper initialized successfully.");
|
||||
}
|
||||
|
||||
/// ✅ FIX H-04: Encrypts a string using AES-256-GCM (new) with random IV
|
||||
String encryptData(String plainText) {
|
||||
try {
|
||||
// Try GCM first (secure mode with random IV)
|
||||
final randomIv = _generateRandomIv(12);
|
||||
// GCM mode in encrypt package handles nonce/IV length automatically (12 bytes recommended)
|
||||
final gcmEncrypter =
|
||||
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm));
|
||||
final encrypted = gcmEncrypter.encrypt(plainText, iv: randomIv);
|
||||
// Prefix with GCM: to distinguish from legacy CBC
|
||||
return '$_gcmPrefix${randomIv.base64}:${encrypted.base64}';
|
||||
} catch (e) {
|
||||
debugPrint('GCM Encryption failed, falling back to CBC: $e');
|
||||
// Fallback to CBC with a warning
|
||||
try {
|
||||
final cbcEncrypter =
|
||||
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
|
||||
final encrypted = cbcEncrypter.encrypt(plainText, iv: iv);
|
||||
return encrypted.base64;
|
||||
} catch (e2) {
|
||||
debugPrint('CBC Encryption Error: $e2');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ✅ FIX H-04: Decrypts a string (supports both GCM and legacy CBC)
|
||||
String decryptData(String encryptedText) {
|
||||
try {
|
||||
// Check if it's GCM format
|
||||
if (encryptedText.startsWith(_gcmPrefix)) {
|
||||
final parts = encryptedText.substring(_gcmPrefix.length).split(':');
|
||||
if (parts.length != 2) {
|
||||
debugPrint('Invalid GCM format, falling back to CBC');
|
||||
return _decryptLegacyCbc(encryptedText);
|
||||
}
|
||||
final iv = encrypt.IV.fromBase64(parts[0]);
|
||||
final encrypted = parts[1];
|
||||
final gcmEncrypter =
|
||||
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.gcm));
|
||||
return gcmEncrypter.decrypt(encrypt.Encrypted.fromBase64(encrypted),
|
||||
iv: iv);
|
||||
}
|
||||
// Legacy CBC format
|
||||
return _decryptLegacyCbc(encryptedText);
|
||||
} catch (e) {
|
||||
debugPrint('Decryption Error: $e');
|
||||
// Try CBC as last resort
|
||||
try {
|
||||
return _decryptLegacyCbc(encryptedText);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy CBC decryption (backward compatibility)
|
||||
String _decryptLegacyCbc(String encryptedText) {
|
||||
final cbcEncrypter =
|
||||
encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
|
||||
final encrypted = encrypt.Encrypted.fromBase64(encryptedText);
|
||||
return cbcEncrypter.decrypt(encrypted, iv: iv);
|
||||
}
|
||||
|
||||
/// Generate cryptographically secure random IV
|
||||
encrypt.IV _generateRandomIv(int length) {
|
||||
final random = Random.secure();
|
||||
final bytes = List<int>.generate(length, (_) => random.nextInt(256));
|
||||
return encrypt.IV(Uint8List.fromList(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
r(String string) {
|
||||
return X.r(X.r(X.r(string, cn), cC), cs).toString();
|
||||
}
|
||||
|
||||
c(String string) {
|
||||
return X.c(X.c(X.c(string, cn), cC), cs).toString();
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:siro_rider/services/geofencing_service.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GeoLocation {
|
||||
Future<Position> getCurrentLocation() async {
|
||||
bool serviceEnabled;
|
||||
LocationPermission permission;
|
||||
|
||||
// Check if location services are enabled.
|
||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
// Location services are not enabled, so we request the user to enable it.
|
||||
return Future.error('Location services are disabled.');
|
||||
}
|
||||
|
||||
permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
// Prominent Disclosure for Background Location
|
||||
bool? userAgreed = await Get.defaultDialog<bool>(
|
||||
title: 'صلاحية الموقع'.tr,
|
||||
middleText: 'يجمع تطبيق سيرو بيانات الموقع لتفعيل ميزة التتبع الجغرافي (Geofence) ولتمكين السائق من الوصول إليك بدقة وتتبع رحلتك حتى عندما يكون التطبيق في الخلفية أو مغلقاً.'.tr,
|
||||
titleStyle: const TextStyle(fontWeight: FontWeight.bold),
|
||||
textConfirm: 'موافق'.tr,
|
||||
textCancel: 'إلغاء'.tr,
|
||||
confirmTextColor: Colors.white,
|
||||
onConfirm: () => Get.back(result: true),
|
||||
onCancel: () => Get.back(result: false),
|
||||
barrierDismissible: false,
|
||||
);
|
||||
|
||||
if (userAgreed != true) {
|
||||
return Future.error('Location permissions are denied');
|
||||
}
|
||||
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
// Permissions are denied, we cannot fetch the location.
|
||||
return Future.error('Location permissions are denied');
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
// Permissions are denied forever, we cannot request permissions.
|
||||
return Future.error(
|
||||
'Location permissions are permanently denied, we cannot request permissions.');
|
||||
}
|
||||
|
||||
// When we reach here, permissions are granted and we can fetch the location.
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.high);
|
||||
|
||||
// Update Geofences based on the fresh location
|
||||
try {
|
||||
SiroGeofencingService.syncZonesWithServer(position.latitude, position.longitude);
|
||||
} catch (e) {
|
||||
// Ignore errors so it doesn't break the main location flow
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import 'package:siro_rider/print.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'dart:io';
|
||||
import 'package:siro_rider/controller/functions/country_logic.dart';
|
||||
|
||||
void showInBrowser(String url) async {
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
launchUrl(Uri.parse(url));
|
||||
} else {}
|
||||
}
|
||||
|
||||
String cleanAndFormatPhoneNumber(String phoneNumber) {
|
||||
String formattedNumber = phoneNumber.replaceAll(RegExp(r'\s+'), '');
|
||||
|
||||
if (formattedNumber.length > 6) {
|
||||
formattedNumber = CountryLogic.formatCurrentCountryPhone(formattedNumber);
|
||||
if (!formattedNumber.startsWith('+')) {
|
||||
formattedNumber = '+$formattedNumber';
|
||||
}
|
||||
}
|
||||
return formattedNumber;
|
||||
}
|
||||
|
||||
Future<void> makePhoneCall(String phoneNumber) async {
|
||||
String formattedNumber = cleanAndFormatPhoneNumber(phoneNumber);
|
||||
// ملاحظة: الأرقام القصيرة (مثل 112) ستتجاوز الشرط أعلاه وتبقى "112" وهو الصحيح
|
||||
|
||||
// 3. التنفيذ (Launch)
|
||||
final Uri launchUri = Uri(
|
||||
scheme: 'tel',
|
||||
path: formattedNumber,
|
||||
);
|
||||
|
||||
try {
|
||||
// استخدام LaunchMode.externalApplication هو الحل الجذري لمشاكل الـ Intent
|
||||
// لأنه يجبر النظام على تسليم الرابط لتطبيق الهاتف بدلاً من محاولة فتحه داخل تطبيقك
|
||||
if (await canLaunchUrl(launchUri)) {
|
||||
await launchUrl(launchUri, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
// في بعض الأجهزة canLaunchUrl تعود بـ false مع الـ tel ومع ذلك يعمل launchUrl
|
||||
// لذا نجرب الإطلاق المباشر كاحتياط
|
||||
await launchUrl(launchUri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
} catch (e) {
|
||||
// طباعة الخطأ في حال الفشل التام
|
||||
Log.print("Error launching call: $e");
|
||||
}
|
||||
}
|
||||
|
||||
void launchCommunication(
|
||||
String method, String contactInfo, String message) async {
|
||||
String formattedContact = cleanAndFormatPhoneNumber(contactInfo);
|
||||
// WhatsApp prefers the phone number without the '+' prefix
|
||||
String whatsappContact = formattedContact.replaceAll('+', '');
|
||||
String url;
|
||||
|
||||
if (Platform.isIOS) {
|
||||
switch (method) {
|
||||
case 'phone':
|
||||
url = 'tel:$formattedContact';
|
||||
break;
|
||||
case 'sms':
|
||||
url = 'sms:$formattedContact?body=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
case 'whatsapp':
|
||||
url =
|
||||
'https://api.whatsapp.com/send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
case 'email':
|
||||
url =
|
||||
'mailto:$formattedContact?subject=Subject&body=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else if (Platform.isAndroid) {
|
||||
switch (method) {
|
||||
case 'phone':
|
||||
url = 'tel:$formattedContact';
|
||||
break;
|
||||
|
||||
case 'sms':
|
||||
url = 'sms:$formattedContact?body=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
case 'whatsapp':
|
||||
// Check if WhatsApp is installed
|
||||
final bool whatsappInstalled =
|
||||
await canLaunchUrl(Uri.parse('whatsapp://'));
|
||||
if (whatsappInstalled) {
|
||||
url =
|
||||
'whatsapp://send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
|
||||
} else {
|
||||
// Provide an alternative action, such as opening the WhatsApp Web API
|
||||
url =
|
||||
'https://api.whatsapp.com/send?phone=$whatsappContact&text=${Uri.encodeComponent(message)}';
|
||||
}
|
||||
break;
|
||||
case 'email':
|
||||
url =
|
||||
'mailto:$formattedContact?subject=Subject&body=${Uri.encodeComponent(message)}';
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await canLaunchUrl(Uri.parse(url))) {
|
||||
await launchUrl(Uri.parse(url));
|
||||
} else {}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// import 'dart:async';
|
||||
|
||||
// import 'package:get/get.dart';
|
||||
// import 'package:location/location.dart';
|
||||
// import 'package:siro_rider/constant/box_name.dart';
|
||||
// import 'package:siro_rider/constant/links.dart';
|
||||
// import 'package:siro_rider/controller/functions/crud.dart';
|
||||
// import 'package:siro_rider/controller/home/payment/captain_wallet_controller.dart';
|
||||
// import 'package:siro_rider/main.dart';
|
||||
|
||||
// // LocationController.dart
|
||||
// class LocationController extends GetxController {
|
||||
// LocationData? _currentLocation;
|
||||
// late Location location;
|
||||
// bool isLoading = false;
|
||||
// late double heading = 0;
|
||||
// late double accuracy = 0;
|
||||
// late double previousTime = 0;
|
||||
// late double latitude;
|
||||
// late double totalDistance = 0;
|
||||
// late double longitude;
|
||||
// late DateTime time;
|
||||
// late double speed = 0;
|
||||
// late double speedAccuracy = 0;
|
||||
// late double headingAccuracy = 0;
|
||||
// bool isActive = false;
|
||||
// late LatLng myLocation;
|
||||
// String totalPoints = '0';
|
||||
// LocationData? get currentLocation => _currentLocation;
|
||||
// Timer? _locationTimer;
|
||||
|
||||
// @override
|
||||
// void onInit() async {
|
||||
// super.onInit();
|
||||
// location = Location();
|
||||
// getLocation();
|
||||
// // startLocationUpdates();
|
||||
|
||||
// totalPoints = Get.put(CaptainWalletController()).totalPoints;
|
||||
// }
|
||||
|
||||
// Future<void> startLocationUpdates() async {
|
||||
// if (box.read(BoxName.driverID) != null) {
|
||||
// _locationTimer =
|
||||
// Timer.periodic(const Duration(seconds: 5), (timer) async {
|
||||
// try {
|
||||
// totalPoints = Get.find<CaptainWalletController>().totalPoints;
|
||||
|
||||
// // if (isActive) {
|
||||
// if (double.parse(totalPoints) > -300) {
|
||||
// await getLocation();
|
||||
|
||||
// // if (box.read(BoxName.driverID) != null) {
|
||||
// await CRUD()
|
||||
// .post(link: AppLink.addCarsLocationByPassenger, payload: {
|
||||
// 'driver_id': box.read(BoxName.driverID).toString(),
|
||||
// 'latitude': myLocation.latitude.toString(),
|
||||
// 'longitude': myLocation.longitude.toString(),
|
||||
// 'heading': heading.toString(),
|
||||
// 'speed': (speed * 3.6).toStringAsFixed(1),
|
||||
// 'distance': totalDistance == 0
|
||||
// ? '0'
|
||||
// : totalDistance < 1
|
||||
// ? totalDistance.toStringAsFixed(3)
|
||||
// : totalDistance.toStringAsFixed(1),
|
||||
// 'status': box.read(BoxName.statusDriverLocation).toString()
|
||||
// });
|
||||
// }
|
||||
|
||||
// // }
|
||||
// } catch (e) {
|
||||
// // Handle the error gracefully
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// void stopLocationUpdates() {
|
||||
// _locationTimer?.cancel();
|
||||
// }
|
||||
|
||||
// Future<void> getLocation() async {
|
||||
// // isLoading = true;
|
||||
// // update();
|
||||
// bool serviceEnabled;
|
||||
// PermissionStatus permissionGranted;
|
||||
|
||||
// // Check if location services are enabled
|
||||
// serviceEnabled = await location.serviceEnabled();
|
||||
// if (!serviceEnabled) {
|
||||
// serviceEnabled = await location.requestService();
|
||||
// if (!serviceEnabled) {
|
||||
// // Location services are still not enabled, handle the error
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Check if the app has permission to access location
|
||||
// permissionGranted = await location.hasPermission();
|
||||
// if (permissionGranted == PermissionStatus.denied) {
|
||||
// permissionGranted = await location.requestPermission();
|
||||
// if (permissionGranted != PermissionStatus.granted) {
|
||||
// // Location permission is still not granted, handle the error
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Configure location accuracy
|
||||
// // LocationAccuracy desiredAccuracy = LocationAccuracy.high;
|
||||
|
||||
// // Get the current location
|
||||
// LocationData _locationData = await location.getLocation();
|
||||
// myLocation =
|
||||
// (_locationData.latitude != null && _locationData.longitude != null
|
||||
// ? LatLng(_locationData.latitude!, _locationData.longitude!)
|
||||
// : null)!;
|
||||
// speed = _locationData.speed!;
|
||||
// heading = _locationData.heading!;
|
||||
// // Calculate the distance between the current location and the previous location
|
||||
// // if (Get.find<HomeCaptainController>().rideId == 'rideId') {
|
||||
// // if (previousTime > 0) {
|
||||
// // double distance = calculateDistanceInKmPerHour(
|
||||
// // previousTime, _locationData.time, speed);
|
||||
// // totalDistance += distance;
|
||||
// // }
|
||||
|
||||
// // previousTime = _locationData.time!;
|
||||
// // }
|
||||
// // Print location details
|
||||
// // isLoading = false;
|
||||
// update();
|
||||
// }
|
||||
|
||||
// double calculateDistanceInKmPerHour(
|
||||
// double? startTime, double? endTime, double speedInMetersPerSecond) {
|
||||
// // Calculate the time difference in hours
|
||||
// double timeDifferenceInHours = (endTime! - startTime!) / 1000 / 3600;
|
||||
|
||||
// // Convert speed to kilometers per hour
|
||||
// double speedInKmPerHour = speedInMetersPerSecond * 3.6;
|
||||
|
||||
// // Calculate the distance in kilometers
|
||||
// double distanceInKilometers = speedInKmPerHour * timeDifferenceInHours;
|
||||
|
||||
// return distanceInKilometers;
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:location/location.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class LocationPermissions {
|
||||
late Location location;
|
||||
|
||||
Future locationPermissions() async {
|
||||
location = Location();
|
||||
var permissionStatus = await location.requestPermission();
|
||||
if (permissionStatus == PermissionStatus.denied) {
|
||||
// The user denied the location permission.
|
||||
Get.defaultDialog(title: 'GPS Required Allow !.'.tr, middleText: '');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/colors.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/functions/crud.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:siro_rider/onbording_page.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.dart';
|
||||
import 'package:siro_rider/views/widgets/my_textField.dart';
|
||||
import 'package:siro_rider/views/widgets/error_snakbar.dart';
|
||||
|
||||
import '../../constant/style.dart';
|
||||
import 'package:siro_rider/controller/home/map/map_socket_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/map_engine_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/location_search_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/nearby_drivers_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/ride_lifecycle_controller.dart';
|
||||
import 'package:siro_rider/controller/home/map/ui_interactions_controller.dart';
|
||||
import 'package:siro_rider/controller/home/menu_controller.dart';
|
||||
import 'package:siro_rider/controller/home/points_for_rider_controller.dart';
|
||||
import 'package:siro_rider/controller/functions/secure_storage.dart';
|
||||
|
||||
class LogOutController extends GetxController {
|
||||
TextEditingController checkTxtController = TextEditingController();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final formKey1 = GlobalKey<FormState>();
|
||||
final emailTextController = TextEditingController();
|
||||
|
||||
Future deleteMyAccountDriver(String id) async {
|
||||
await CRUD().post(link: AppLink.removeUser, payload: {'id': id}).then(
|
||||
(value) => mySnackbarSuccess('Your Account is Deleted'.tr));
|
||||
}
|
||||
|
||||
checkBeforeDelete() async {
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.deletecaptainAccounr,
|
||||
payload: {'id': box.read(BoxName.driverID)}).then((value) => exit(0));
|
||||
}
|
||||
|
||||
deletecaptainAccount() {
|
||||
Get.defaultDialog(
|
||||
backgroundColor: AppColor.yellowColor,
|
||||
title: 'Are you sure to delete your account?'.tr,
|
||||
middleText:
|
||||
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month ',
|
||||
titleStyle: AppStyle.title,
|
||||
content: Column(
|
||||
children: [
|
||||
Container(
|
||||
width: Get.width,
|
||||
decoration: AppStyle.boxDecoration,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Your data will be erased after 2 weeks\nAnd you will can\'t return to use app after 1 month'
|
||||
.tr,
|
||||
style: AppStyle.title.copyWith(color: AppColor.redColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Form(
|
||||
key: formKey,
|
||||
child: SizedBox(
|
||||
width: Get.width,
|
||||
child: MyTextForm(
|
||||
controller: checkTxtController,
|
||||
label: 'Enter Your First Name'.tr,
|
||||
hint: 'Enter Your First Name'.tr,
|
||||
type: TextInputType.name,
|
||||
),
|
||||
))
|
||||
],
|
||||
),
|
||||
confirm: MyElevatedButton(
|
||||
title: 'Delete'.tr,
|
||||
onPressed: () {
|
||||
if (checkTxtController.text == box.read(BoxName.nameDriver)) {
|
||||
deletecaptainAccount();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
Future logOutPassenger() async {
|
||||
Get.defaultDialog(
|
||||
title: 'Are you Sure to LogOut?'.tr,
|
||||
content: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
MyElevatedButton(
|
||||
title: 'Cancel'.tr,
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(AppColor.redColor),
|
||||
),
|
||||
onPressed: () {
|
||||
// box.remove(BoxName.agreeTerms);
|
||||
box.remove(BoxName.passengerPhotoUrl);
|
||||
box.remove(BoxName.driverID);
|
||||
box.remove(BoxName.email);
|
||||
box.remove(BoxName.lang);
|
||||
box.remove(BoxName.name);
|
||||
box.remove(BoxName.passengerID);
|
||||
box.remove(BoxName.phone);
|
||||
box.remove(BoxName.tokenFCM);
|
||||
box.remove(BoxName.tokens);
|
||||
box.remove(BoxName.addHome);
|
||||
box.remove(BoxName.addWork);
|
||||
box.remove(BoxName.agreeTerms);
|
||||
box.remove(BoxName.apiKeyRun);
|
||||
box.remove(BoxName.countryCode);
|
||||
|
||||
SecureStorage().deleteData(BoxName.cardNumber);
|
||||
SecureStorage().deleteData(BoxName.cvvCode);
|
||||
SecureStorage().deleteData(BoxName.cardHolderName);
|
||||
SecureStorage().deleteData(BoxName.expiryDate);
|
||||
|
||||
box.remove(BoxName.passengerWalletTotal);
|
||||
box.remove(BoxName.isVerified);
|
||||
Get.delete<MapSocketController>(force: true);
|
||||
Get.delete<MapEngineController>(force: true);
|
||||
Get.delete<LocationSearchController>(force: true);
|
||||
Get.delete<NearbyDriversController>(force: true);
|
||||
Get.delete<RideLifecycleController>(force: true);
|
||||
Get.delete<UiInteractionsController>(force: true);
|
||||
Get.delete<MyMenuController>(force: true);
|
||||
Get.delete<CRUD>(force: true);
|
||||
Get.delete<WayPointController>(force: true);
|
||||
Get.offAll(OnBoardingPage());
|
||||
},
|
||||
child: Text(
|
||||
'Sign Out'.tr,
|
||||
style:
|
||||
AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
||||
))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Future logOutCaptain() async {
|
||||
Get.defaultDialog(
|
||||
title: 'Are you Sure to LogOut?'.tr,
|
||||
titleStyle: AppStyle.title,
|
||||
content: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
MyElevatedButton(
|
||||
title: 'Cancel'.tr,
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all(AppColor.redColor),
|
||||
),
|
||||
onPressed: () {
|
||||
// box.remove(BoxName.agreeTerms);
|
||||
box.remove(BoxName.driverID);
|
||||
box.remove(BoxName.sexDriver);
|
||||
box.remove(BoxName.dobDriver);
|
||||
box.remove(BoxName.nameDriver);
|
||||
box.remove(BoxName.emailDriver);
|
||||
box.remove(BoxName.phoneDriver);
|
||||
box.remove(BoxName.statusDriverLocation);
|
||||
box.remove(BoxName.cvvCodeDriver);
|
||||
box.remove(BoxName.lastNameDriver);
|
||||
box.remove(BoxName.passwordDriver);
|
||||
box.remove(BoxName.cardNumberDriver);
|
||||
box.remove(BoxName.expiryDateDriver);
|
||||
box.remove(BoxName.cardHolderNameDriver);
|
||||
box.remove(BoxName.vin);
|
||||
box.remove(BoxName.make);
|
||||
box.remove(BoxName.year);
|
||||
box.remove(BoxName.owner);
|
||||
box.remove(BoxName.onBoarding);
|
||||
box.remove(BoxName.agreeTerms);
|
||||
Get.offAll(OnBoardingPage());
|
||||
},
|
||||
child: Text(
|
||||
'Sign Out'.tr,
|
||||
style:
|
||||
AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
||||
))
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
deletePassengerAccount() async {
|
||||
if (formKey1.currentState!.validate()) {
|
||||
if (box.read(BoxName.email).toString() == emailTextController.text) {
|
||||
await CRUD().post(link: AppLink.passengerRemovedAccountEmail, payload: {
|
||||
'email': box.read(BoxName.email),
|
||||
});
|
||||
} else {
|
||||
mySnackbarError('Email you inserted is Wrong.'.tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'net_guard.dart';
|
||||
|
||||
typedef BodyEncoder = Future<http.Response> Function();
|
||||
|
||||
class HttpRetry {
|
||||
/// ريتراي لـ network/transient errors فقط.
|
||||
static Future<http.Response> sendWithRetry(
|
||||
BodyEncoder send, {
|
||||
int maxRetries = 3,
|
||||
Duration baseDelay = const Duration(milliseconds: 400),
|
||||
Duration timeout = const Duration(seconds: 12),
|
||||
}) async {
|
||||
// ✅ Pre-flight check for internet connection
|
||||
if (!await NetGuard().hasInternet()) {
|
||||
// Immediately throw a specific exception if there's no internet.
|
||||
// This avoids pointless retries.
|
||||
throw const SocketException("No internet connection");
|
||||
}
|
||||
int attempt = 0;
|
||||
while (true) {
|
||||
attempt++;
|
||||
try {
|
||||
final res = await send().timeout(timeout);
|
||||
return res;
|
||||
} on TimeoutException catch (_) {
|
||||
if (attempt >= maxRetries) rethrow;
|
||||
} on SocketException catch (_) {
|
||||
if (attempt >= maxRetries) rethrow;
|
||||
} on HandshakeException catch (_) {
|
||||
if (attempt >= maxRetries) rethrow;
|
||||
} on http.ClientException catch (e) {
|
||||
// مثال: Connection reset by peer
|
||||
final msg = e.message.toLowerCase();
|
||||
final transient = msg.contains('connection reset') ||
|
||||
msg.contains('broken pipe') ||
|
||||
msg.contains('timed out');
|
||||
if (!transient || attempt >= maxRetries) rethrow;
|
||||
}
|
||||
// backoff: 0.4s, 0.8s, 1.6s
|
||||
final delay = baseDelay * (1 << (attempt - 1));
|
||||
await Future.delayed(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:internet_connection_checker/internet_connection_checker.dart';
|
||||
|
||||
class NetGuard {
|
||||
static final NetGuard _i = NetGuard._();
|
||||
NetGuard._();
|
||||
factory NetGuard() => _i;
|
||||
|
||||
bool _notified = false;
|
||||
|
||||
/// فحص: (أ) فيه شبكة؟ (ب) فيه انترنت؟ (ج) السيرفر نفسه reachable؟
|
||||
Future<bool> hasInternet({Uri? mustReach}) async {
|
||||
final connectivity = await Connectivity().checkConnectivity();
|
||||
if (connectivity == ConnectivityResult.none) return false;
|
||||
|
||||
final hasNet =
|
||||
await InternetConnectionChecker.createInstance().hasConnection;
|
||||
if (!hasNet) return false;
|
||||
|
||||
if (mustReach != null) {
|
||||
try {
|
||||
final host = mustReach.host;
|
||||
final result = await InternetAddress.lookup(host);
|
||||
if (result.isEmpty || result.first.rawAddress.isEmpty) return false;
|
||||
|
||||
// اختباري خفيف عبر TCP (80/443) — 400ms timeout
|
||||
final port = mustReach.scheme == 'http' ? 80 : 443;
|
||||
final socket = await Socket.connect(host, port,
|
||||
timeout: const Duration(milliseconds: 400));
|
||||
socket.destroy();
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// إظهار إشعار مرة واحدة ثم إسكات التكرارات
|
||||
void notifyOnce(void Function(String title, String msg) show) {
|
||||
if (_notified) return;
|
||||
_notified = true;
|
||||
show('لا يوجد اتصال بالإنترنت', 'تحقق من الشبكة ثم حاول مجددًا.');
|
||||
// إعادة السماح بعد 15 ثانية
|
||||
Future.delayed(const Duration(seconds: 15), () => _notified = false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/colors.dart';
|
||||
import '../../main.dart';
|
||||
import '../../print.dart';
|
||||
import 'encrypt_decrypt.dart';
|
||||
|
||||
Future<void> checkForUpdate(BuildContext context) async {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final currentVersion = packageInfo.buildNumber;
|
||||
final version = packageInfo.version;
|
||||
Log.print('currentVersion is : $currentVersion');
|
||||
// Fetch the latest version from your server
|
||||
String latestVersion = box.read(BoxName.package)?.toString() ?? '';
|
||||
box.write(BoxName.packagInfo, version);
|
||||
|
||||
if (latestVersion.isNotEmpty && latestVersion != currentVersion) {
|
||||
showUpdateDialog(context);
|
||||
}
|
||||
}
|
||||
|
||||
checkForBounusInvitation() {
|
||||
if (box.read(BoxName.inviteCode) != null) {}
|
||||
}
|
||||
|
||||
// Future<String> getPackageInfo() async {
|
||||
// final response = await CRUD().get(link: AppLink.packageInfo, payload: {
|
||||
// "platform": Platform.isAndroid ? 'android' : 'ios',
|
||||
// "appName": AppInformation.appName,
|
||||
// });
|
||||
|
||||
// if (response != 'failure') {
|
||||
// return jsonDecode(response)['message'][0]['version'];
|
||||
// }
|
||||
// return '';
|
||||
// }
|
||||
// getDeviceFingerprint() async {
|
||||
// final deviceInfo = await DeviceInfoPlugin().deviceInfo;
|
||||
// var deviceData;
|
||||
|
||||
// if (Platform.isAndroid) {
|
||||
// deviceData = deviceInfo.data;
|
||||
// Log.print('deviceData: ${jsonEncode(deviceData)}');
|
||||
// } else if (Platform.isIOS) {
|
||||
// deviceData = deviceInfo.data;
|
||||
// }
|
||||
|
||||
// final String deviceId =
|
||||
// deviceData['device'] ?? deviceData['identifierForVendor'];
|
||||
// final String deviceModel = deviceData['model'];
|
||||
// final String osVersion = deviceData['systemVersion'];
|
||||
|
||||
// Log.print('fingr: ${'${deviceId}_${deviceModel}_$osVersion'}');
|
||||
// Log.print('deviceModel: ${deviceModel}');
|
||||
// Log.print('osVersion: ${osVersion}');
|
||||
// return EncryptionHelper.instance
|
||||
// .encryptData('${deviceId}_${deviceModel}_$osVersion');
|
||||
// }
|
||||
|
||||
void showUpdateDialog(BuildContext context) {
|
||||
final String storeUrl = Platform.isAndroid
|
||||
? 'https://play.google.com/store/apps/details?id=com.Siro.siro'
|
||||
: 'https://apps.apple.com/jo/app/siro-rider/id6748075179';
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
barrierColor: Colors.black.withOpacity(0.5),
|
||||
pageBuilder: (_, __, ___) {
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||
child: Center(
|
||||
child: AlertDialog(
|
||||
// Using AlertDialog for a more Material Design look
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(16)), // More rounded corners
|
||||
elevation: 4, // Add a bit more elevation
|
||||
contentPadding: EdgeInsets.zero, // Remove default content padding
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: Image.asset(
|
||||
'assets/images/logo.png',
|
||||
height: 72, // Slightly larger logo
|
||||
width: 72,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Text(
|
||||
'Update Available'.tr,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
// Use theme's title style
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Text(
|
||||
'A new version of the app is available. Please update to the latest version.'
|
||||
.tr, // More encouraging message
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
// Use theme's body style
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(height: 0),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
// Using TextButton for "Cancel"
|
||||
onPressed: () => Navigator.pop(context),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.grey,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text('Cancel'.tr),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 48,
|
||||
child: VerticalDivider(width: 0), // Using VerticalDivider
|
||||
),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
// Using ElevatedButton for "Update"
|
||||
onPressed: () async {
|
||||
if (await canLaunchUrl(Uri.parse(storeUrl))) {
|
||||
await launchUrl(Uri.parse(storeUrl));
|
||||
}
|
||||
if (context.mounted) Navigator.pop(context);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColor
|
||||
.primaryColor, // Use theme's primary color
|
||||
foregroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary, // Use theme's onPrimary color
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text('Update'.tr),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
transitionBuilder: (_, animation, __, child) {
|
||||
return ScaleTransition(
|
||||
scale: CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutCubic, // More natural curve
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class DeviceHelper {
|
||||
static Future<String> getDeviceFingerprint() async {
|
||||
// ── التحقق من وجود بصمة مخزّنة مسبقاً ──────────────────────
|
||||
// AES-GCM يستخدم IV عشوائي كل مرة، فالتشفير ينتج نتيجة مختلفة
|
||||
// حتى لو النص الأصلي نفسه. لذلك نخزّن البصمة المشفرة أول مرة
|
||||
// ونرجعها من التخزين في كل مرة بعدها لضمان الثبات.
|
||||
final String? cachedFp = box.read(BoxName.deviceFpEncrypted);
|
||||
if (cachedFp != null && cachedFp.isNotEmpty) {
|
||||
return cachedFp;
|
||||
}
|
||||
|
||||
final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
|
||||
var deviceData;
|
||||
|
||||
try {
|
||||
if (Platform.isAndroid) {
|
||||
// Fetch Android-specific device information
|
||||
AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
|
||||
deviceData = androidInfo.toMap(); // Convert to a map for easier access
|
||||
// Log.print('deviceData: ${jsonEncode(deviceData)}');
|
||||
} else if (Platform.isIOS) {
|
||||
// Fetch iOS-specific device information
|
||||
IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
|
||||
deviceData = iosInfo.toMap(); // Convert to a map for easier access
|
||||
} else {
|
||||
throw UnsupportedError('Unsupported platform');
|
||||
}
|
||||
|
||||
// Extract relevant device information
|
||||
final String deviceId = Platform.isAndroid
|
||||
? deviceData['androidId'] ?? deviceData['fingerprint'] ?? 'unknown'
|
||||
: deviceData['identifierForVendor'] ?? 'unknown';
|
||||
|
||||
final String deviceModel = deviceData['model'] ?? 'unknown';
|
||||
// final String osVersion = Platform.isAndroid
|
||||
// ? deviceData['version']['release'] ?? 'unknown'
|
||||
// : deviceData['systemVersion'] ?? 'unknown';
|
||||
|
||||
// Log the extracted information
|
||||
|
||||
// Generate and return the encrypted fingerprint
|
||||
final String fingerprint = '${deviceId}_$deviceModel';
|
||||
final String encryptedFp =
|
||||
EncryptionHelper.instance.encryptData(fingerprint);
|
||||
box.write(BoxName.deviceFpEncrypted, encryptedFp);
|
||||
//Log.print(EncryptionHelper.instance.encryptData(fingerprint));
|
||||
return encryptedFp;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to generate device fingerprint');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// import 'package:ride/controller/functions/crud.dart';
|
||||
|
||||
// class RemoveAccount {
|
||||
|
||||
// void removeAccount()async{
|
||||
// var res=await CRUD().post(link: link)
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,25 @@
|
||||
// import 'package:credit_card_scanner/credit_card_scanner.dart';
|
||||
// import 'package:get/get.dart';
|
||||
//
|
||||
// class ScanIdCard extends GetxController {
|
||||
// CardDetails? _cardDetails;
|
||||
// CardScanOptions scanOptions = const CardScanOptions(
|
||||
// scanCardHolderName: true,
|
||||
// enableDebugLogs: true,
|
||||
// validCardsToScanBeforeFinishingScan: 5,
|
||||
// possibleCardHolderNamePositions: [
|
||||
// CardHolderNameScanPosition.aboveCardNumber,
|
||||
// ],
|
||||
// );
|
||||
//
|
||||
// Future<void> scanCard() async {
|
||||
// final CardDetails? cardDetails =
|
||||
// await CardScanner.scanCard(scanOptions: scanOptions);
|
||||
// if (cardDetails == null) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// _cardDetails = cardDetails;
|
||||
// update();
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/info.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:jwt_decoder/jwt_decoder.dart';
|
||||
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import 'crud.dart';
|
||||
import 'encrypt_decrypt.dart';
|
||||
|
||||
class SecureStorage {
|
||||
void saveData(String key, value) async {
|
||||
await storage.write(key: key, value: value);
|
||||
}
|
||||
|
||||
Future<String?> readData(String boxName) async {
|
||||
final String? value = await storage.read(key: boxName);
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
Future<void> deleteData(String key) async {
|
||||
await storage.delete(key: key);
|
||||
}
|
||||
}
|
||||
|
||||
class AppInitializer {
|
||||
List<Map<String, dynamic>> links = [];
|
||||
|
||||
/// تقرأ JWT من FlutterSecureStorage أولاً، مع fallback إلى GetStorage
|
||||
Future<String?> _readJwt() async {
|
||||
try {
|
||||
final secureJwt = await storage.read(key: 'jwt');
|
||||
if (secureJwt != null && secureJwt.isNotEmpty) return secureJwt;
|
||||
} catch (_) {}
|
||||
return box.read(BoxName.jwt);
|
||||
}
|
||||
|
||||
Future<void> initializeApp() async {
|
||||
final jwt = await _readJwt();
|
||||
if (jwt == null) {
|
||||
await LoginController().getJWT();
|
||||
} else {
|
||||
bool isTokenExpired =
|
||||
JwtDecoder.isExpired(r(jwt).toString().split(AppInformation.addd)[0]);
|
||||
|
||||
if (isTokenExpired) {
|
||||
await LoginController().getJWT();
|
||||
}
|
||||
}
|
||||
|
||||
// await getKey();
|
||||
}
|
||||
|
||||
getAIKey(String key1) async {
|
||||
if (box.read(BoxName.firstTimeLoadKey) == null) {
|
||||
var res =
|
||||
await CRUD().get(link: AppLink.getapiKey, payload: {"keyName": key1});
|
||||
if (res != 'failure') {
|
||||
var d = res['message'];
|
||||
storage.write(key: key1, value: d[key1].toString());
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getKey() async {
|
||||
try {
|
||||
var res =
|
||||
await CRUD().get(link: AppLink.getLocationAreaLinks, payload: {});
|
||||
if (res != 'failure') {
|
||||
links = List<Map<String, dynamic>>.from(res['message']);
|
||||
await box.remove(BoxName.locationName);
|
||||
await box.remove(BoxName.basicLink);
|
||||
await box.remove(links[4]['name']);
|
||||
await box.remove(links[1]['name']);
|
||||
await box.remove(links[2]['name']);
|
||||
await box.write(BoxName.locationName, links);
|
||||
await box.write(BoxName.basicLink, (links[0]['server_link']));
|
||||
await box.write(links[2]['name'], (links[2]['server_link']));
|
||||
await box.write(links[1]['name'], (links[3]['server_link']));
|
||||
await box.write(links[3]['name'], (links[1]['server_link']));
|
||||
await box.write(BoxName.paymentLink, (links[4]['server_link']));
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:siro_rider/print.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../main.dart';
|
||||
|
||||
class SecurityChecks {
|
||||
static const platform = MethodChannel(
|
||||
'com.Siro.siro/security'); // Choose a unique channel name
|
||||
|
||||
static Future<bool> isDeviceCompromised() async {
|
||||
try {
|
||||
final bool result = await platform
|
||||
.invokeMethod('isNativeRooted'); // Invoke the native method
|
||||
return result;
|
||||
} on PlatformException catch (e) {
|
||||
Log.print("Failed to check security status: ${e.message}");
|
||||
return false; // Platform not supported → treat as secure (not compromised)
|
||||
} catch (e) {
|
||||
Log.print("Security check error: $e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static isDeviceRootedFromNative(BuildContext context) async {
|
||||
bool compromised = await isDeviceCompromised();
|
||||
if (compromised) {
|
||||
showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text("Security Warning".tr),
|
||||
content: Text(
|
||||
"Your device appears to be compromised. The app will now close."
|
||||
.tr),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
SystemNavigator.pop(); // Close the app
|
||||
},
|
||||
child: Text("OK"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// Continue with normal app flow
|
||||
|
||||
box.write(BoxName.security_check, 'passed');
|
||||
|
||||
Log.print("Device is secure.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:siro_rider/constant/api_key.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/constant/info.dart';
|
||||
import 'package:siro_rider/constant/links.dart';
|
||||
import 'package:siro_rider/controller/auth/login_controller.dart';
|
||||
import 'package:siro_rider/env/env.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'package:siro_rider/views/widgets/elevated_btn.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import '../../print.dart';
|
||||
import 'crud.dart';
|
||||
|
||||
class SmsEgyptController extends GetxController {
|
||||
var headers = {'Content-Type': 'application/json'};
|
||||
Future<String> getSender() async {
|
||||
var res = await CRUD().get(link: AppLink.getSender, payload: {});
|
||||
if (res != 'failure') {
|
||||
var d = res['message'][0]['senderId'].toString();
|
||||
return d;
|
||||
} else {
|
||||
return "Sefer Egy";
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> sendSmsEgypt(String phone, otp) async {
|
||||
// String sender = await getSender();
|
||||
// var body = jsonEncode({
|
||||
// "username": 'Sefer',
|
||||
// "password": AK.smsPasswordEgypt,
|
||||
// "message": "${AppInformation.appName} app code is $otp\ncopy it to app",
|
||||
// "language": box.read(BoxName.lang) == 'en' ? "e" : 'r',
|
||||
// "sender": sender, //"Sefer Egy",
|
||||
// "receiver": phone
|
||||
// });
|
||||
|
||||
var res = await CRUD().post(link: AppLink.sendSmsFromPHP, payload: {
|
||||
"language": box.read(BoxName.lang) == 'en' ? "e" : 'r',
|
||||
"receiver": phone,
|
||||
});
|
||||
if (res != 'failure') {
|
||||
// var res = await http.post(
|
||||
// Uri.parse(AppLink.sendSms),
|
||||
// body: body,
|
||||
// headers: headers,
|
||||
// );
|
||||
|
||||
// else if (jsonDecode(res)['message'].toString() ==
|
||||
// "Invalid Sender with Connection") {
|
||||
//
|
||||
// }
|
||||
// else {
|
||||
Get.defaultDialog(
|
||||
title: 'You will receive a code in SMS message'.tr,
|
||||
middleText: '',
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
}));
|
||||
} else {
|
||||
await CRUD().post(link: AppLink.updatePhoneInvalidSMSPassenger, payload: {
|
||||
"phone_number":
|
||||
'+2${Get.find<LoginController>().phoneController.text}'
|
||||
});
|
||||
box.write(BoxName.phoneDriver,
|
||||
'+2${Get.find<LoginController>().phoneController.text}');
|
||||
box.write(BoxName.isVerified, '1');
|
||||
|
||||
await Get.put(LoginController()).loginUsingCredentials(
|
||||
box.read(BoxName.driverID).toString(),
|
||||
box.read(BoxName.emailDriver).toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future checkCredit(String phone, otp) async {
|
||||
var res = await http.post(
|
||||
Uri.parse(AppLink.checkCredit),
|
||||
body: {
|
||||
"username": AppInformation.appName,
|
||||
"password": AK.smsPasswordEgypt,
|
||||
},
|
||||
headers: headers,
|
||||
);
|
||||
}
|
||||
|
||||
Future sendSmsWithValidaty(String phone, otp) async {
|
||||
var res = await http.post(
|
||||
Uri.parse(AppLink.checkCredit),
|
||||
body: {
|
||||
"username": AppInformation.appName,
|
||||
"password": AK.smsPasswordEgypt,
|
||||
"message": "This is an example SMS message.",
|
||||
"language": box.read(BoxName.lang) == 'en' ? "e" : 'r',
|
||||
"sender": "Kazumi", // todo add sefer sender name
|
||||
"receiver": "2$phone",
|
||||
"validity": "10",
|
||||
"StartTime": DateTime.now().toString() // "1/1/2024 10:00:00"
|
||||
},
|
||||
headers: headers,
|
||||
);
|
||||
}
|
||||
|
||||
Future sendSmsStatus(String smsid) async {
|
||||
var res = await http.post(
|
||||
Uri.parse(AppLink.checkCredit),
|
||||
body: {
|
||||
"username": AppInformation.appName,
|
||||
"password": AK.smsPasswordEgypt,
|
||||
"smsid": smsid //"00b77dfc-5b8f-474d-9def-9f0158b70f98"
|
||||
},
|
||||
headers: headers,
|
||||
);
|
||||
}
|
||||
|
||||
Future sendWhatsAppAuth(String to, String token) async {
|
||||
var headers = {
|
||||
'Authorization': 'Bearer ${Env.whatsapp}',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
var request = http.Request(
|
||||
'POST',
|
||||
Uri.parse(
|
||||
'https://graph.facebook.com/v20.0/${Env.whatappID}/messages'));
|
||||
request.body = json.encode({
|
||||
"messaging_product": "whatsapp",
|
||||
"to": to, //"962798583052",
|
||||
"type": "template",
|
||||
"template": {
|
||||
"name": "sefer1",
|
||||
"language": {"code": "en"},
|
||||
"components": [
|
||||
{
|
||||
"type": "body",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": token,
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
request.headers.addAll(headers);
|
||||
|
||||
http.StreamedResponse response = await request.send();
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
print(await response.stream.bytesToString());
|
||||
Get.defaultDialog(
|
||||
title: 'You will receive a code in WhatsApp Messenger'.tr,
|
||||
middleText: '',
|
||||
confirm: MyElevatedButton(
|
||||
title: 'OK'.tr,
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
}));
|
||||
} else {
|
||||
print(response.reasonPhrase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart' as http_io;
|
||||
|
||||
class SslPinning {
|
||||
SslPinning._();
|
||||
|
||||
static final Map<String, List<String>> _pins = {
|
||||
'intaleq.xyz': [
|
||||
'/tNRUeeLxUhQU5gbgdpVWC6QBGAqc/ujg8Kcf0wQiAM=',
|
||||
'Hlx/0EWNDH5Xkt2KzvqxUzbw0vvEsyZSlibialSyGqI=',
|
||||
],
|
||||
'siromove.com': [
|
||||
'C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=',
|
||||
'diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=',
|
||||
],
|
||||
};
|
||||
|
||||
static final List<String> _globalPins = [
|
||||
'Ex/Od4QBaJmloAIDqe/IDxjrvXVYBxftwVU1gJMINuw=',
|
||||
'lrzsBiZJdvN0YHeazyjFp8/oo8Cq4RqP/O4FwL3fCMY=',
|
||||
'aXKbjhWobvwXelevtxcd/GSt0owvyozxUH40RTzLFHA=',
|
||||
];
|
||||
|
||||
static http.Client createPinnedClient() {
|
||||
final httpClient = HttpClient()
|
||||
..badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) {
|
||||
final derHash = base64.encode(sha256.convert(cert.der).bytes);
|
||||
for (final entry in _pins.entries) {
|
||||
if (host == entry.key || host.endsWith('.${entry.key}')) {
|
||||
if (entry.value.contains(derHash)) return true;
|
||||
}
|
||||
}
|
||||
if (_globalPins.contains(derHash)) return true;
|
||||
return false;
|
||||
};
|
||||
return http_io.IOClient(httpClient);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
||||
|
||||
import '../../constant/char_map.dart';
|
||||
import '../../main.dart';
|
||||
|
||||
class Sss {
|
||||
static read(String boxname) async {
|
||||
return box.read(X.r(X.r(X.r(boxname, cn), cC), cs));
|
||||
}
|
||||
|
||||
static write(String boxname, value) async {
|
||||
return box.write(boxname, X.c(X.c(X.c(value, cn), cC), cs));
|
||||
}
|
||||
|
||||
static delete(String boxname) async {
|
||||
return box.remove(boxname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/colors.dart';
|
||||
import 'package:siro_rider/constant/style.dart';
|
||||
|
||||
class Toast {
|
||||
static void show(BuildContext context, String message, Color color) {
|
||||
final snackBar = SnackBar(
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
backgroundColor: color,
|
||||
elevation: 3,
|
||||
content: Text(
|
||||
message,
|
||||
style: AppStyle.title.copyWith(color: AppColor.secondaryColor),
|
||||
),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
animation: const AlwaysStoppedAnimation(1.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10.0), // Custom border radius
|
||||
),
|
||||
width: Get.width * .8,
|
||||
// shape: const StadiumBorder(
|
||||
// side: BorderSide(
|
||||
// color: AppColor.secondaryColor,
|
||||
// width: 1.0,
|
||||
// style: BorderStyle.solid,
|
||||
// )),
|
||||
duration: const Duration(seconds: 2),
|
||||
);
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
snackBar,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class TranslateHelper {
|
||||
static Future<String> translateText(String text, String targetLang) async {
|
||||
if (text.isEmpty) return text;
|
||||
try {
|
||||
final url = Uri.parse(
|
||||
'https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=$targetLang&dt=t&q=${Uri.encodeComponent(text)}'
|
||||
);
|
||||
final response = await http.get(url);
|
||||
if (response.statusCode == 200) {
|
||||
final decoded = jsonDecode(response.body);
|
||||
if (decoded != null && decoded is List && decoded.isNotEmpty && decoded[0] is List) {
|
||||
final List parts = decoded[0];
|
||||
String translated = '';
|
||||
for (var part in parts) {
|
||||
if (part is List && part.isNotEmpty) {
|
||||
translated += part[0].toString();
|
||||
}
|
||||
}
|
||||
return translated;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to original text on any exception
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'dart:io';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_tts/flutter_tts.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../main.dart';
|
||||
|
||||
class TextToSpeechController extends GetxController {
|
||||
final flutterTts = FlutterTts();
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
initTts();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
flutterTts.stop(); // Stop any ongoing TTS
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// Initialize TTS engine with language check
|
||||
Future<void> initTts() async {
|
||||
try {
|
||||
String langCode = box.read(BoxName.lang) ?? 'ar-SA';
|
||||
if (langCode == 'ar') langCode = 'ar-SA';
|
||||
if (langCode == 'en') langCode = 'en-US';
|
||||
|
||||
bool isAvailable = await flutterTts.isLanguageAvailable(langCode);
|
||||
if (!isAvailable) {
|
||||
if (langCode.startsWith('ar')) {
|
||||
// Try to find ANY available Arabic language
|
||||
List<dynamic> languages = await flutterTts.getLanguages;
|
||||
String? fallbackAr = languages
|
||||
.cast<String>()
|
||||
.firstWhere((l) => l.startsWith('ar'), orElse: () => '');
|
||||
if (fallbackAr != null && fallbackAr.isNotEmpty) {
|
||||
langCode = fallbackAr;
|
||||
isAvailable = true;
|
||||
}
|
||||
}
|
||||
if (!isAvailable) langCode = 'en-US';
|
||||
}
|
||||
|
||||
await flutterTts.setLanguage(langCode);
|
||||
await flutterTts.setSpeechRate(0.5);
|
||||
await flutterTts.setVolume(1.0);
|
||||
|
||||
if (Platform.isIOS) {
|
||||
await flutterTts.setIosAudioCategory(
|
||||
IosTextToSpeechAudioCategory.playback,
|
||||
[
|
||||
IosTextToSpeechAudioCategoryOptions.mixWithOthers,
|
||||
IosTextToSpeechAudioCategoryOptions.duckOthers,
|
||||
],
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
print('TTS Init Error: $error');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to speak the given text (stops current speech first)
|
||||
Future<void> speakText(String text) async {
|
||||
if (text.isEmpty) return;
|
||||
try {
|
||||
await flutterTts.stop();
|
||||
await flutterTts.speak(text);
|
||||
} catch (error) {
|
||||
print('Failed to speak text: $error');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// import 'package:ride/constant/credential.dart';
|
||||
// import 'package:twilio_flutter/twilio_flutter.dart';
|
||||
//
|
||||
// class TwilioSMS {
|
||||
// TwilioFlutter twilioFlutter = TwilioFlutter(
|
||||
// accountSid: AppCredintials.accountSIDTwillo,
|
||||
// authToken: AppCredintials.authTokenTwillo,
|
||||
// twilioNumber: '+962 7 9858 3052');
|
||||
//
|
||||
// Future<void> sendSMS({
|
||||
// required String recipientPhoneNumber,
|
||||
// required String message,
|
||||
// }) async {
|
||||
// try {
|
||||
// await twilioFlutter.sendSMS(
|
||||
// toNumber: recipientPhoneNumber,
|
||||
// messageBody: message,
|
||||
// );
|
||||
// } catch (e) {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,108 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:siro_rider/constant/api_key.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:image_cropper/image_cropper.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:secure_string_operations/secure_string_operations.dart';
|
||||
import 'package:siro_rider/views/widgets/error_snakbar.dart';
|
||||
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/char_map.dart';
|
||||
import '../../constant/colors.dart';
|
||||
import '../../constant/info.dart';
|
||||
import '../../main.dart';
|
||||
import 'encrypt_decrypt.dart';
|
||||
|
||||
class ImageController extends GetxController {
|
||||
File? myImage;
|
||||
bool isloading = false;
|
||||
CroppedFile? croppedFile;
|
||||
final picker = ImagePicker();
|
||||
var image;
|
||||
choosImage(String link, String imageType) async {
|
||||
final pickedImage = await picker.pickImage(source: ImageSource.gallery);
|
||||
image = File(pickedImage!.path);
|
||||
croppedFile = await ImageCropper().cropImage(
|
||||
sourcePath: image!.path,
|
||||
uiSettings: [
|
||||
AndroidUiSettings(
|
||||
toolbarTitle: 'Cropper'.tr,
|
||||
toolbarColor: AppColor.blueColor,
|
||||
toolbarWidgetColor: AppColor.yellowColor,
|
||||
initAspectRatio: CropAspectRatioPreset.original,
|
||||
lockAspectRatio: false),
|
||||
IOSUiSettings(
|
||||
title: 'Cropper'.tr,
|
||||
),
|
||||
],
|
||||
);
|
||||
myImage = File(pickedImage.path);
|
||||
isloading = true;
|
||||
update();
|
||||
// Save the cropped image
|
||||
File savedCroppedImage = File(croppedFile!.path);
|
||||
try {
|
||||
await uploadImage(
|
||||
savedCroppedImage,
|
||||
{
|
||||
'driverID':
|
||||
box.read(BoxName.driverID) ?? box.read(BoxName.passengerID),
|
||||
'imageType': imageType
|
||||
},
|
||||
link,
|
||||
);
|
||||
} catch (e) {
|
||||
mySnackbarError(e.toString());
|
||||
} finally {
|
||||
isloading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
uploadImage(File file, Map data, String link) async {
|
||||
var request = http.MultipartRequest(
|
||||
'POST',
|
||||
Uri.parse(link), //'https://ride.mobile-app.store/uploadImage1.php'
|
||||
);
|
||||
|
||||
var length = await file.length();
|
||||
var stream = http.ByteStream(file.openRead());
|
||||
var multipartFile = http.MultipartFile(
|
||||
'image',
|
||||
stream,
|
||||
length,
|
||||
filename: basename(file.path),
|
||||
);
|
||||
final String fingerPrint =
|
||||
box.read(BoxName.deviceFpEncrypted)?.toString() ?? '';
|
||||
request.headers.addAll({
|
||||
'Authorization':
|
||||
'Bearer ${r(box.read(BoxName.jwt)).toString().split(AppInformation.addd)[0]}',
|
||||
'X-Device-FP': fingerPrint,
|
||||
});
|
||||
// Set the file name to the driverID
|
||||
request.files.add(
|
||||
http.MultipartFile(
|
||||
'image',
|
||||
stream,
|
||||
length,
|
||||
filename: '${box.read(BoxName.driverID)}.jpg',
|
||||
),
|
||||
);
|
||||
data.forEach((key, value) {
|
||||
request.fields[key] = value;
|
||||
});
|
||||
var myrequest = await request.send();
|
||||
var res = await http.Response.fromStream(myrequest);
|
||||
if (res.statusCode == 200) {
|
||||
return jsonDecode(res.body);
|
||||
} else {
|
||||
throw Exception(
|
||||
'Failed to upload image: ${res.statusCode} - ${res.body}');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user