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:
Hamza-Ayed
2026-07-27 05:14:13 +03:00
co-authored by Claude Opus 5
parent 9909d9b4c1
commit 4d8414c96b
3198 changed files with 766859 additions and 0 deletions
@@ -0,0 +1,124 @@
/// Database schema for click_payment table.
///
/// Stores Cliq wallet payment transactions for manual verification.
///
/// SQL (MySQL):
/// ```sql
/// CREATE TABLE click_payment (
/// id INT AUTO_INCREMENT PRIMARY KEY,
/// user_id VARCHAR(64) NOT NULL COMMENT 'Passenger ID from passengers table',
/// user_type VARCHAR(20) NOT NULL DEFAULT 'passenger',
/// amount DECIMAL(10,2) NOT NULL,
/// currency VARCHAR(6) NOT NULL DEFAULT 'JOD',
/// click_phone VARCHAR(20) NOT NULL COMMENT 'Phone number used for Cliq invoice',
/// invoice_number VARCHAR(64) NOT NULL UNIQUE COMMENT 'Unique invoice reference',
/// cliq_alias VARCHAR(64) NOT NULL COMMENT 'Cliq wallet alias for payment',
/// invoice_status ENUM('pending','completed','failed','expired') NOT NULL DEFAULT 'pending',
/// proof_text TEXT DEFAULT NULL COMMENT 'Bank SMS proof pasted by user',
/// proof_image VARCHAR(256) DEFAULT NULL COMMENT 'Screenshot path (optional)',
/// verified_by VARCHAR(64) DEFAULT NULL COMMENT 'Admin who verified manually',
/// verified_at DATETIME DEFAULT NULL,
/// created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
/// updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
/// INDEX idx_user (user_id),
/// INDEX idx_status (invoice_status),
/// INDEX idx_invoice (invoice_number)
/// );
/// ```
class ClickPaymentSchema {
static const String tableName = 'click_payment';
static const String createTable = '''
CREATE TABLE click_payment (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
user_type VARCHAR(20) NOT NULL DEFAULT 'passenger',
amount DECIMAL(10,2) NOT NULL,
currency VARCHAR(6) NOT NULL DEFAULT 'JOD',
click_phone VARCHAR(20) NOT NULL,
invoice_number VARCHAR(64) NOT NULL UNIQUE,
cliq_alias VARCHAR(64) NOT NULL,
invoice_status ENUM('pending','completed','failed','expired') NOT NULL DEFAULT 'pending',
proof_text TEXT DEFAULT NULL,
proof_image VARCHAR(256) DEFAULT NULL,
verified_by VARCHAR(64) DEFAULT NULL,
verified_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_status (invoice_status),
INDEX idx_invoice (invoice_number)
)
''';
/// Map a JSON response from the API to a typed object.
final int? id;
final String userId;
final String userType;
final double amount;
final String currency;
final String clickPhone;
final String invoiceNumber;
final String cliqAlias;
final String invoiceStatus;
final String? proofText;
final String? proofImage;
final String? verifiedBy;
final DateTime? verifiedAt;
final DateTime? createdAt;
final DateTime? updatedAt;
const ClickPaymentSchema({
this.id,
required this.userId,
this.userType = 'passenger',
required this.amount,
this.currency = 'JOD',
required this.clickPhone,
required this.invoiceNumber,
required this.cliqAlias,
this.invoiceStatus = 'pending',
this.proofText,
this.proofImage,
this.verifiedBy,
this.verifiedAt,
this.createdAt,
this.updatedAt,
});
factory ClickPaymentSchema.fromJson(Map<String, dynamic> json) {
return ClickPaymentSchema(
id: json['id'] is int ? json['id'] : int.tryParse(json['id']?.toString() ?? ''),
userId: json['user_id']?.toString() ?? '',
userType: json['user_type']?.toString() ?? 'passenger',
amount: (json['amount'] is num) ? (json['amount'] as num).toDouble() : double.tryParse(json['amount']?.toString() ?? '0') ?? 0,
currency: json['currency']?.toString() ?? 'JOD',
clickPhone: json['click_phone']?.toString() ?? '',
invoiceNumber: json['invoice_number']?.toString() ?? '',
cliqAlias: json['cliq_alias']?.toString() ?? '',
invoiceStatus: json['invoice_status']?.toString() ?? 'pending',
proofText: json['proof_text']?.toString(),
proofImage: json['proof_image']?.toString(),
verifiedBy: json['verified_by']?.toString(),
verifiedAt: json['verified_at'] != null ? DateTime.tryParse(json['verified_at'].toString()) : null,
createdAt: json['created_at'] != null ? DateTime.tryParse(json['created_at'].toString()) : null,
updatedAt: json['updated_at'] != null ? DateTime.tryParse(json['updated_at'].toString()) : null,
);
}
Map<String, dynamic> toJson() => {
if (id != null) 'id': id,
'user_id': userId,
'user_type': userType,
'amount': amount,
'currency': currency,
'click_phone': clickPhone,
'invoice_number': invoiceNumber,
'cliq_alias': cliqAlias,
'invoice_status': invoiceStatus,
if (proofText != null) 'proof_text': proofText,
if (proofImage != null) 'proof_image': proofImage,
if (verifiedBy != null) 'verified_by': verifiedBy,
if (verifiedAt != null) 'verified_at': verifiedAt!.toIso8601String(),
};
}
+139
View File
@@ -0,0 +1,139 @@
import 'dart:convert';
import 'package:siro_rider/constant/table_names.dart';
import 'package:siro_rider/main.dart';
class DbSql {
static final DbSql instance = DbSql._();
DbSql._();
// Helper to read data as a list of maps from GetStorage
List<Map<String, dynamic>> _readTable(String table) {
String? dataString = box.read(table);
if (dataString == null) return [];
try {
List<dynamic> parsed = jsonDecode(dataString);
return parsed.map((e) => Map<String, dynamic>.from(e)).toList();
} catch (e) {
return [];
}
}
// Helper to write data back to GetStorage
void _writeTable(String table, List<Map<String, dynamic>> data) {
box.write(table, jsonEncode(data));
}
Future<List<Map<String, dynamic>>> getAllData(String table) async {
return _readTable(table);
}
Future<List<Map<String, dynamic>>> getCustomQuery(String query) async {
String q = query.toLowerCase();
String targetTable = TableName.recentLocations; // Default target
// Determine the table
if (q.contains(TableName.recentLocations.toLowerCase())) {
targetTable = TableName.recentLocations;
} else if (q.contains(TableName.carLocations.toLowerCase())) {
targetTable = TableName.carLocations;
} else if (q.contains(TableName.placesFavorite.toLowerCase())) {
targetTable = TableName.placesFavorite;
}
List<Map<String, dynamic>> data = _readTable(targetTable);
// Apply DISTINCT logic if needed based on query
if (q.contains('distinct latitude, longitude, name, rate')) {
// Manual distinct by name and location
Map<String, Map<String, dynamic>> distinctMap = {};
for (var item in data) {
String key = '${item['latitude']}_${item['longitude']}_${item['name']}';
if (!distinctMap.containsKey(key)) {
distinctMap[key] = item;
}
}
data = distinctMap.values.toList();
}
// Apply ORDER BY createdAt DESC logic
if (q.contains('order by createdat desc')) {
data.sort((a, b) {
String dateA = a['createdAt'] ?? '';
String dateB = b['createdAt'] ?? '';
return dateB.compareTo(dateA);
});
}
return data;
}
Future<int> insertData(Map<String, dynamic> map, String table) async {
List<Map<String, dynamic>> data = _readTable(table);
// Generate simple ID
int newId = data.isEmpty ? 1 : (data.last['id'] as int? ?? 0) + 1;
Map<String, dynamic> newMap = Map<String, dynamic>.from(map);
newMap['id'] = newId;
data.add(newMap);
_writeTable(table, data);
return newId;
}
Future<int> insertMapLocation(Map<String, dynamic> map, String table) async {
List<Map<String, dynamic>> data = _readTable(table);
// Check if exists
int existingIndex = data.indexWhere((element) =>
element['latitude'] == map['latitude'] &&
element['longitude'] == map['longitude'] &&
element['name'] == map['name']);
if (existingIndex != -1) {
// Update
data[existingIndex] = Map<String, dynamic>.from(data[existingIndex]);
data[existingIndex]['createdAt'] = DateTime.now().toIso8601String();
for (var key in map.keys) {
if (key != 'latitude' && key != 'longitude' && key != 'name') {
data[existingIndex][key] = map[key];
}
}
_writeTable(table, data);
return data[existingIndex]['id'] as int? ?? 1;
} else {
// Insert
int newId = data.isEmpty ? 1 : ((data.last['id'] as int?) ?? 0) + 1;
Map<String, dynamic> newMap = Map<String, dynamic>.from(map);
newMap['id'] = newId;
newMap['createdAt'] = DateTime.now().toIso8601String();
data.add(newMap);
_writeTable(table, data);
return newId;
}
}
Future<int> updateData(Map<String, dynamic> map, String table, int id) async {
List<Map<String, dynamic>> data = _readTable(table);
int index = data.indexWhere((element) => element['id'] == id);
if (index != -1) {
data[index] = {...data[index], ...map};
_writeTable(table, data);
return 1;
}
return 0;
}
Future<int> deleteData(String table, int id) async {
List<Map<String, dynamic>> data = _readTable(table);
int initialLength = data.length;
data.removeWhere((element) => element['id'] == id);
if (data.length < initialLength) {
_writeTable(table, data);
return 1;
}
return 0;
}
Future<int> deleteAllData(String table) async {
_writeTable(table, []);
return 1;
}
}
@@ -0,0 +1,21 @@
class MonthlyDataModel {
final int year;
final int month;
final int day;
final int ridesCount;
MonthlyDataModel({
required this.year,
required this.month,
required this.day,
required this.ridesCount,
});
factory MonthlyDataModel.fromJson(Map<String, dynamic> json) =>
MonthlyDataModel(
year: json['year'] as int,
month: json['month'] as int,
day: json['day'] as int,
ridesCount: json['rides_count'] as int,
);
}
@@ -0,0 +1,79 @@
class Passenger {
String id;
String phone;
String email;
String gender;
String status;
String birthdate;
String site;
String firstName;
String lastName;
String sosPhone;
String education;
String employmentType;
String maritalStatus;
String createdAt;
String updatedAt;
int countPassenger;
int countFeedback;
double ratingPassenger;
int countDriverRate;
int countPassengerCancel;
double passengerAverageRating;
int countPassengerRate;
int countPassengerRide;
Passenger({
required this.id,
required this.phone,
required this.email,
required this.gender,
required this.status,
required this.birthdate,
required this.site,
required this.firstName,
required this.lastName,
required this.sosPhone,
required this.education,
required this.employmentType,
required this.maritalStatus,
required this.createdAt,
required this.updatedAt,
required this.countPassenger,
required this.countFeedback,
required this.ratingPassenger,
required this.countDriverRate,
required this.countPassengerCancel,
required this.passengerAverageRating,
required this.countPassengerRate,
required this.countPassengerRide,
});
factory Passenger.fromJson(Map<String, dynamic> json) {
return Passenger(
id: json['id'],
phone: json['phone'],
email: json['email'],
gender: json['gender'],
status: json['status'],
birthdate: json['birthdate'],
site: json['site'],
firstName: json['first_name'],
lastName: json['last_name'],
sosPhone: json['sosPhone'],
education: json['education'],
employmentType: json['employmentType'],
maritalStatus: json['maritalStatus'],
createdAt: json['created_at'],
updatedAt: json['updated_at'],
countPassenger: json['countPassenger'],
countFeedback: json['countFeedback'],
ratingPassenger: json['ratingPassenger'].toDouble(),
countDriverRate: json['countDriverRate'],
countPassengerCancel: json['countPassengerCancel'],
passengerAverageRating: json['passengerAverageRating'].toDouble(),
countPassengerRate: json['countPassengerRate'],
countPassengerRide: json['countPassengerRide'],
);
}
}
@@ -0,0 +1,12 @@
class MonthlyDataModel {
int day;
int totalDuration;
MonthlyDataModel({required this.day, required this.totalDuration});
factory MonthlyDataModel.fromJson(Map<String, dynamic> json) =>
MonthlyDataModel(
day: int.parse(json['day'].toString().split('-')[2]),
totalDuration:
int.parse(json['total_duration'].toString().split(':')[0]));
}
@@ -0,0 +1,34 @@
class CarLocationModel {
String id;
String driverId;
double latitude;
double heading;
double speed;
double longitude;
DateTime createdAt;
DateTime updatedAt;
CarLocationModel({
required this.id,
required this.driverId,
required this.latitude,
required this.longitude,
required this.heading,
required this.speed,
required this.createdAt,
required this.updatedAt,
});
factory CarLocationModel.fromJson(Map<String, dynamic> json) {
return CarLocationModel(
id: json['id'],
driverId: json['driver_id'],
latitude: double.parse(json['latitude'].toString()),
longitude: double.parse(json['longitude'].toString()),
heading: double.parse(json['heading'].toString()),
speed: double.parse(json['speed'].toString()),
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
);
}
}
@@ -0,0 +1,30 @@
import 'package:get/get.dart';
List<OnBoardingModel> onBoardingList = [
OnBoardingModel(
title: 'Welcome to Siro!'.tr,
image: 'assets/images/on1.png',
body:
'Siro is the ride-hailing app that is safe, reliable, and accessible.'
.tr,
),
OnBoardingModel(
title: 'Get to your destination quickly and easily.'.tr,
image: 'assets/images/on2.png',
body: 'With Siro, you can get a ride to your destination in minutes.'.tr,
),
OnBoardingModel(
title: 'Enjoy a safe and comfortable ride.'.tr,
image: 'assets/images/on3.png',
body:
'Siro is committed to safety, and all of our captains are carefully screened and background checked.'
.tr,
),
];
class OnBoardingModel {
final String? title;
final String? image;
final String? body;
OnBoardingModel({this.body, this.title, this.image});
}
@@ -0,0 +1,272 @@
import 'dart:math' as math;
import 'package:siro_rider/constant/box_name.dart';
import 'package:siro_rider/main.dart';
import 'package:siro_rider/splash_screen_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
class CouponPainter extends CustomPainter {
final Color primaryColor;
final Color secondaryColor;
CouponPainter({
required this.primaryColor,
required this.secondaryColor,
});
@override
void paint(Canvas canvas, Size size) {
final Paint primaryPaint = Paint()
..color = primaryColor
..style = PaintingStyle.fill; //
final Paint secondaryPaint = Paint()
..color = secondaryColor
..style = PaintingStyle.fill;
final Path path = Path();
// Draw the main ticket shape
path.moveTo(0, size.height * 0.1);
path.lineTo(size.width * 0.93, size.height * 0.1);
path.arcToPoint(
Offset(size.width, size.height * 0.2),
radius: const Radius.circular(20),
clockwise: false,
);
path.lineTo(size.width, size.height * 0.8);
path.arcToPoint(
Offset(size.width * 0.93, size.height * 0.9),
radius: const Radius.circular(20),
clockwise: false,
);
path.lineTo(0, size.height * 0.9);
path.close();
canvas.drawPath(path, primaryPaint);
// Draw decorative circles on the left side
for (int i = 0; i < 5; i++) {
canvas.drawCircle(
Offset(0, size.height * (0.2 + i * 0.15)),
10,
secondaryPaint,
);
}
// Draw a wavy pattern on the right side
final wavePaint = Paint()
..color = secondaryColor.withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = 2;
for (int i = 0; i < 20; i++) {
canvas.drawLine(
Offset(size.width * 0.8, i * 10.0),
Offset(
size.width,
i * 10.0 + math.sin(i * 0.5) * 10,
),
wavePaint,
);
}
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
class PromoBanner extends StatelessWidget {
final String promoCode;
final String discountPercentage;
final String validity;
const PromoBanner({
Key? key,
required this.promoCode,
required this.discountPercentage,
required this.validity,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: CouponPainter(
primaryColor: Colors.blue[800]!,
secondaryColor: Colors.white,
),
child: Container(
width: 320,
height: 240,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const SizedBox(
height: 10,
),
Text(
'Enter the promo code and get'.tr,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
Text(
'${'DISCOUNT'.tr} $discountPercentage ${'for'.tr} $validity'
.toUpperCase(),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 10),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Text(
promoCode,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue[800],
),
),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {
// Copy promo code to clipboard
Clipboard.setData(ClipboardData(text: promoCode));
// Show a Snackbar or other feedback to the user
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Promo code copied to clipboard!'.tr)),
);
box.write(BoxName.isGiftToken, '1');
box.write(BoxName.isFirstTime, '1');
Get.back();
},
style: ElevatedButton.styleFrom(
foregroundColor: Colors.blue[800], // Customize the color
backgroundColor: Colors.white, // Customize the background color
),
child: Text('Copy Code'.tr),
)
],
),
),
);
}
}
// import 'package:siro_rider/constant/colors.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter/material.dart';
// class CouponPainter extends CustomPainter {
// @override
// void paint(Canvas canvas, Size size) {
// final Paint paint = Paint()
// ..color = AppColor.blueColor
// ..style = PaintingStyle.fill;
// final Path path = Path();
// // Draw the ticket shape (like the image)
// path.moveTo(0, 0);
// path.lineTo(size.width * 0.7, 0); // top left to top right edge
// // Draw curve for the cut on the right side (ticket look)
// path.arcToPoint(Offset(size.width, size.height * 0.15),
// radius: const Radius.circular(15), clockwise: false);
// path.lineTo(size.width, size.height * 0.85);
// path.arcToPoint(Offset(size.width * 0.7, size.height),
// radius: const Radius.circular(15), clockwise: false);
// path.lineTo(0, size.height);
// canvas.drawPath(path, paint);
// }
// @override
// bool shouldRepaint(CustomPainter oldDelegate) {
// return false;
// }
// }
// class PromoBanner extends StatelessWidget {
// final String promoCode;
// final String discountPercentage;
// final String validity;
// const PromoBanner({
// required this.promoCode,
// required this.discountPercentage,
// required this.validity,
// });
// @override
// Widget build(BuildContext context) {
// return CustomPaint(
// painter: CouponPainter(),
// child: Container(
// width: 300, // Fixed width for the promo banner
// height: 180, // Set the desired height for your banner
// padding: const EdgeInsets.all(16),
// child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// Text(
// 'Enter the promo code and get'.toUpperCase(),
// style: const TextStyle(
// fontSize: 16,
// fontWeight: FontWeight.bold,
// color: Colors.white,
// ),
// textAlign: TextAlign.center,
// ),
// Text(
// '$discountPercentage OFF for $validity'.toUpperCase(),
// style: const TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Colors.white,
// ),
// textAlign: TextAlign.center,
// ),
// const SizedBox(height: 10),
// Container(
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(10),
// ),
// padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
// child: Text(
// promoCode,
// style: TextStyle(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// color: Colors.blue[800],
// ),
// ),
// ),
// ],
// ),
// ),
// );
// }
// }