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,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(),
|
||||
};
|
||||
}
|
||||
@@ -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],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
Reference in New Issue
Block a user