Update: 2026-07-03 01:48:00
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/views/home/my_wallet/payment_history_passenger_page.dart';
|
||||
import 'package:siro_rider/views/home/my_wallet/siro_prime_page.dart';
|
||||
import 'dart:ui'; // لاستخدام تأثيرات متقدمة
|
||||
|
||||
import '../../../constant/box_name.dart';
|
||||
@@ -12,6 +13,7 @@ import '../../../constant/style.dart';
|
||||
import '../../../controller/functions/toast.dart';
|
||||
import '../../../controller/home/payment/credit_card_controller.dart';
|
||||
import '../../../controller/payment/payment_controller.dart';
|
||||
import '../../../controller/payment/prime_controller.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/elevated_btn.dart';
|
||||
import '../../widgets/my_scafold.dart';
|
||||
@@ -26,6 +28,7 @@ class PassengerWallet extends StatelessWidget {
|
||||
// نفس منطق استدعاء الكنترولرز
|
||||
Get.put(PaymentController());
|
||||
Get.put(CreditCardController());
|
||||
Get.put(PrimeController());
|
||||
|
||||
return MyScafolld(
|
||||
title: 'My Balance'.tr,
|
||||
@@ -75,6 +78,10 @@ class PassengerWallet extends StatelessWidget {
|
||||
onTap: () => _showWalletPhoneDialog(context,
|
||||
Get.find<PaymentController>()), // نفس دالتك القديمة
|
||||
),
|
||||
|
||||
// 👑 Siro Prime Banner
|
||||
const SizedBox(height: 8),
|
||||
_buildPrimeBanner(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -258,6 +265,85 @@ class PassengerWallet extends StatelessWidget {
|
||||
barrierDismissible: false,
|
||||
);
|
||||
}
|
||||
|
||||
// 👑 Siro Prime Banner Widget
|
||||
Widget _buildPrimeBanner() {
|
||||
return GetBuilder<PrimeController>(
|
||||
builder: (ctrl) {
|
||||
final bool isPrime = ctrl.isPrime;
|
||||
return GestureDetector(
|
||||
onTap: () => Get.to(
|
||||
() => const SiroPrimePage(),
|
||||
transition: Transition.rightToLeftWithFade,
|
||||
),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
gradient: LinearGradient(
|
||||
colors: isPrime
|
||||
? [const Color(0xFF7C3AED), const Color(0xFFDB2777)]
|
||||
: [const Color(0xFF1E1E2E), const Color(0xFF2A2A3E)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: (isPrime ? const Color(0xFF7C3AED) : Colors.black)
|
||||
.withOpacity(0.35),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.15),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Text('👑', style: TextStyle(fontSize: 24)),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
isPrime ? 'Siro Prime Active'.tr : 'Siro Prime'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
isPrime
|
||||
? '${'Valid until'.tr}: ${ctrl.expireAt?.substring(0, 10) ?? ''}'
|
||||
: 'No surge pricing. Priority rides. Tap to learn more.'.tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
isPrime ? Icons.verified : Icons.chevron_right,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// الكلاس القديم CardSiroWallet لم نعد بحاجة إليه لأنه تم دمجه وتطويره
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_rider/constant/box_name.dart';
|
||||
import 'package:siro_rider/controller/payment/prime_controller.dart';
|
||||
import 'package:siro_rider/main.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
class SiroPrimePage extends StatefulWidget {
|
||||
const SiroPrimePage({super.key});
|
||||
|
||||
@override
|
||||
State<SiroPrimePage> createState() => _SiroPrimePageState();
|
||||
}
|
||||
|
||||
class _SiroPrimePageState extends State<SiroPrimePage>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _shimmerController;
|
||||
late AnimationController _pulseController;
|
||||
late Animation<double> _pulseAnimation;
|
||||
|
||||
final PrimeController ctrl = Get.find<PrimeController>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_shimmerController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 2),
|
||||
)..repeat();
|
||||
|
||||
_pulseController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 1500),
|
||||
)..repeat(reverse: true);
|
||||
|
||||
_pulseAnimation = Tween<double>(begin: 0.95, end: 1.05).animate(
|
||||
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_shimmerController.dispose();
|
||||
_pulseController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFF0D0D1A),
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
// ─── Animated Header ────────────────────────────────────
|
||||
SliverAppBar(
|
||||
expandedHeight: 280,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
pinned: true,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: _buildHeader(),
|
||||
),
|
||||
),
|
||||
|
||||
// ─── Content ─────────────────────────────────────────────
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Status Card
|
||||
_buildStatusCard(),
|
||||
const SizedBox(height: 28),
|
||||
|
||||
// Benefits Title
|
||||
Text(
|
||||
'What you get with Prime'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Benefits List
|
||||
_buildBenefit(
|
||||
icon: '🚫',
|
||||
title: 'No Surge Pricing'.tr,
|
||||
subtitle: 'Never pay extra during peak hours. Your price stays fixed regardless of demand.'.tr,
|
||||
gradient: [const Color(0xFF7C3AED), const Color(0xFF4F46E5)],
|
||||
),
|
||||
_buildBenefit(
|
||||
icon: '⚡',
|
||||
title: 'Priority Matching'.tr,
|
||||
subtitle: 'Your ride request reaches drivers who have destinations near you first.'.tr,
|
||||
gradient: [const Color(0xFFDB2777), const Color(0xFF9333EA)],
|
||||
),
|
||||
_buildBenefit(
|
||||
icon: '👑',
|
||||
title: 'Prime Badge'.tr,
|
||||
subtitle: 'Drivers can see your Prime status, making them more likely to accept your request quickly.'.tr,
|
||||
gradient: [const Color(0xFFD97706), const Color(0xFFEF4444)],
|
||||
),
|
||||
_buildBenefit(
|
||||
icon: '💰',
|
||||
title: 'Save Every Ride'.tr,
|
||||
subtitle: 'During rush hours, surge pricing can add 50–200% to your fare. Prime protects you completely.'.tr,
|
||||
gradient: [const Color(0xFF059669), const Color(0xFF0891B2)],
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// Price Card
|
||||
_buildPriceCard(),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Subscribe Button
|
||||
_buildSubscribeButton(),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Fine Print
|
||||
Text(
|
||||
'• Subscription renews automatically every 30 days\n• Cancel anytime from your wallet page\n• Amount is deducted from your in-app wallet'.tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.4),
|
||||
fontSize: 12,
|
||||
height: 1.8,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Header ──────────────────────────────────────────────────
|
||||
Widget _buildHeader() {
|
||||
return AnimatedBuilder(
|
||||
animation: _shimmerController,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Color(0xFF1A0A2E), Color(0xFF0D0D1A)],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Background particles
|
||||
..._buildParticles(),
|
||||
|
||||
// Crown + Title
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 50),
|
||||
ScaleTransition(
|
||||
scale: _pulseAnimation,
|
||||
child: Container(
|
||||
width: 90,
|
||||
height: 90,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF7C3AED), Color(0xFFDB2777)],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF7C3AED).withOpacity(0.6),
|
||||
blurRadius: 30,
|
||||
spreadRadius: 5,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Center(
|
||||
child: Text('👑', style: TextStyle(fontSize: 44)),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ShaderMask(
|
||||
shaderCallback: (bounds) => const LinearGradient(
|
||||
colors: [Color(0xFFE879F9), Color(0xFF818CF8), Color(0xFFF59E0B)],
|
||||
).createShader(bounds),
|
||||
child: const Text(
|
||||
'SIRO PRIME',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w900,
|
||||
color: Colors.white,
|
||||
letterSpacing: 4,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'The Premium Ride Experience'.tr,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildParticles() {
|
||||
return List.generate(8, (i) {
|
||||
final angle = (i / 8) * 2 * math.pi;
|
||||
final radius = 120.0 + (i % 3) * 30;
|
||||
final size = 3.0 + (i % 4).toDouble();
|
||||
final opacity =
|
||||
(0.1 + (_shimmerController.value + i / 8) % 1.0 * 0.5).clamp(0.0, 0.6);
|
||||
return Positioned(
|
||||
left: MediaQuery.of(context).size.width / 2 + math.cos(angle) * radius - 5,
|
||||
top: 140 + math.sin(angle) * 80,
|
||||
child: Opacity(
|
||||
opacity: opacity,
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFE879F9),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Status Card ──────────────────────────────────────────────
|
||||
Widget _buildStatusCard() {
|
||||
return GetBuilder<PrimeController>(
|
||||
builder: (c) {
|
||||
if (!c.isPrime) return const SizedBox.shrink();
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF7C3AED), Color(0xFFDB2777)],
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.verified, color: Colors.white, size: 28),
|
||||
const SizedBox(width: 12),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('You are a Prime member!'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
if (c.expireAt != null)
|
||||
Text(
|
||||
'${'Active until'.tr}: ${c.expireAt!.substring(0, 10)}',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.8), fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Benefit Card ────────────────────────────────────────────
|
||||
Widget _buildBenefit({
|
||||
required String icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required List<Color> gradient,
|
||||
}) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.white.withOpacity(0.05),
|
||||
border: Border.all(color: Colors.white.withOpacity(0.08)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
gradient: LinearGradient(colors: gradient),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(icon, style: const TextStyle(fontSize: 22)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 15)),
|
||||
const SizedBox(height: 4),
|
||||
Text(subtitle,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.55), fontSize: 13, height: 1.5)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Price Card ───────────────────────────────────────────────
|
||||
Widget _buildPriceCard() {
|
||||
final country = box.read(BoxName.countryCode) ?? 'Jordan';
|
||||
final pricing = {
|
||||
'Jordan': {'price': '3', 'currency': 'JOD'},
|
||||
'Egypt': {'price': '200', 'currency': 'EGP'},
|
||||
'Syria': {'price': '500', 'currency': 'SYP'},
|
||||
};
|
||||
final p = pricing[country] ?? pricing['Jordan']!;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(color: const Color(0xFF7C3AED).withOpacity(0.4), width: 1.5),
|
||||
color: const Color(0xFF7C3AED).withOpacity(0.08),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Monthly Subscription'.tr,
|
||||
style: TextStyle(color: Colors.white.withOpacity(0.6), fontSize: 13)),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
p['price']!,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 48,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Text(
|
||||
p['currency']!,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFE879F9),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
'≈ \$4 USD / ${'month'.tr}',
|
||||
style: TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Subscribe Button ─────────────────────────────────────────
|
||||
Widget _buildSubscribeButton() {
|
||||
return GetBuilder<PrimeController>(
|
||||
builder: (c) {
|
||||
if (c.isPrime) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.green.withOpacity(0.15),
|
||||
border: Border.all(color: Colors.green.withOpacity(0.4)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.check_circle, color: Colors.green),
|
||||
const SizedBox(width: 8),
|
||||
Text('Already subscribed'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.green, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: c.isLoading ? null : () => c.subscribePrime(context),
|
||||
child: AnimatedBuilder(
|
||||
animation: _shimmerController,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
gradient: LinearGradient(
|
||||
colors: const [Color(0xFF7C3AED), Color(0xFFDB2777), Color(0xFFD97706)],
|
||||
stops: [
|
||||
0,
|
||||
(_shimmerController.value + 0.5) % 1.0,
|
||||
1,
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF7C3AED).withOpacity(0.5),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: c.isLoading
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white, strokeWidth: 2),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('👑', style: TextStyle(fontSize: 20)),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Subscribe Now'.tr,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user