Update codebase

This commit is contained in:
Hamza-Ayed
2026-08-09 16:56:13 +03:00
parent 95e2e4f35d
commit b64debaa88
1058 changed files with 164327 additions and 113928 deletions
@@ -0,0 +1,311 @@
// behavior_and_achievements.dart — سلوك القيادة والإنجازات
//
// ‏نُقلا من `statistics_dashboard.dart` (حيث كانا دالتين خاصّتين) إلى
// ‏ودجتين مستقلّتين عند دمج شاشتَي الأرباح والإحصائيات في شاشة واحدة.
// ‏نسخهما كان سيعني نسختين تفترقان عند أول تعديل.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
import '../../../../controller/gamification/gamification_controller.dart';
class BehaviorSection extends StatelessWidget {
final GamificationController gc;
const BehaviorSection({super.key, required this.gc});
@override
Widget build(BuildContext context) {
final bool isExcellent = gc.behaviorScore >= 90;
final bool isGood = gc.behaviorScore >= 75 && gc.behaviorScore < 90;
final Color statusColor = isExcellent
? Colors.green
: isGood
? Colors.orange
: Colors.red;
final String statusText = isExcellent
? 'Excellent'.tr
: isGood
? 'Good'.tr
: 'Needs Improvement'.tr;
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: FinanceDesignSystem.cardColor,
borderRadius: BorderRadius.circular(24),
boxShadow: const [
BoxShadow(color: Colors.black12, blurRadius: 10, offset: Offset(0, 4))
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Icon(Icons.shield_rounded,
color: FinanceDesignSystem.accentBlue),
const SizedBox(width: 8),
Text('Driving Behavior'.tr,
style: FinanceDesignSystem.headingStyle),
],
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
statusText,
style: TextStyle(
color: statusColor,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_statItem(
title: 'Score'.tr,
value: '${gc.behaviorScore}%',
icon: Icons.speed_rounded,
color: FinanceDesignSystem.accentBlue,
),
_statItem(
title: 'Max Speed'.tr,
value: '${gc.maxSpeed.toStringAsFixed(0)} km/h',
icon: Icons.directions_car_rounded,
color: Colors.orange,
),
_statItem(
title: 'Hard Brakes'.tr,
value: '${gc.hardBrakes}',
icon: Icons.warning_rounded,
color: Colors.red,
),
],
),
const SizedBox(height: 16),
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: LinearProgressIndicator(
value: gc.behaviorScore / 100,
backgroundColor: FinanceDesignSystem.backgroundColor,
valueColor: AlwaysStoppedAnimation<Color>(statusColor),
minHeight: 8,
),
),
],
),
);
}
Widget _statItem({
required String title,
required String value,
required IconData icon,
required Color color,
}) =>
Column(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(height: 8),
Text(value,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: FinanceDesignSystem.primaryDark)),
Text(title,
style: TextStyle(
fontSize: 12, color: FinanceDesignSystem.textSecondary)),
],
);
}
class AchievementsSection extends StatelessWidget {
final GamificationController gc;
const AchievementsSection({super.key, required this.gc});
@override
Widget build(BuildContext context) {
final unlocked = gc.achievements.where((a) => a.isUnlocked).toList();
final locked = gc.achievements.where((a) => !a.isUnlocked).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Achievements'.tr, style: FinanceDesignSystem.headingStyle),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: FinanceDesignSystem.accentBlue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
'${gc.unlockedCount}/${gc.totalAchievements}',
style: TextStyle(
color: FinanceDesignSystem.accentBlue,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 16),
if (unlocked.isNotEmpty) ...[
...unlocked.map((a) => _AchievementCard(ach: a, unlocked: true)),
const SizedBox(height: 16),
],
...locked.map((a) => _AchievementCard(ach: a, unlocked: false)),
],
);
}
}
class _AchievementCard extends StatelessWidget {
final Achievement ach;
final bool unlocked;
const _AchievementCard({required this.ach, required this.unlocked});
@override
Widget build(BuildContext context) {
final isAr = Get.locale?.languageCode == 'ar';
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
// ‏كان اللون هنا Colors.white ثابتاً — يختفي النص عليه في الوضع
// ‏الداكن. نستعمل لون البطاقة من نظام التصميم.
color: unlocked
? FinanceDesignSystem.cardColor
: FinanceDesignSystem.cardColor.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
border: unlocked
? Border.all(color: ach.color.withValues(alpha: 0.3), width: 1.5)
: Border.all(color: FinanceDesignSystem.borderColor),
boxShadow: unlocked
? [
BoxShadow(
color: ach.color.withValues(alpha: 0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
]
: null,
),
child: Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: unlocked
? ach.color.withValues(alpha: 0.15)
: FinanceDesignSystem.textMuted.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(14),
),
child: Icon(
ach.icon,
color: unlocked ? ach.color : FinanceDesignSystem.textMuted,
size: 26,
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isAr ? ach.titleAr : ach.titleEn,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: unlocked
? FinanceDesignSystem.primaryDark
: FinanceDesignSystem.textSecondary,
),
),
const SizedBox(height: 2),
Text(
isAr ? ach.descriptionAr : ach.descriptionEn,
style: TextStyle(
fontSize: 11,
color: FinanceDesignSystem.textSecondary),
),
if (!unlocked) ...[
const SizedBox(height: 8),
Stack(
children: [
Container(
height: 6,
decoration: BoxDecoration(
color: FinanceDesignSystem.textMuted
.withValues(alpha: 0.25),
borderRadius: BorderRadius.circular(3),
),
),
FractionallySizedBox(
widthFactor: ach.progress,
child: Container(
height: 6,
decoration: BoxDecoration(
color: ach.color.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(3),
),
),
),
],
),
const SizedBox(height: 4),
Text(
'${ach.currentProgress}/${ach.target}',
style: TextStyle(
fontSize: 10,
color: FinanceDesignSystem.textMuted,
fontWeight: FontWeight.w600,
),
),
],
],
),
),
if (unlocked)
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: ach.color.withValues(alpha: 0.15),
shape: BoxShape.circle,
),
child: Icon(Icons.check_rounded, color: ach.color, size: 18),
)
else
Icon(Icons.lock_outline_rounded,
color: FinanceDesignSystem.textMuted, size: 20),
],
),
);
}
}
@@ -1,4 +1,4 @@
import 'package:tripz_driver/constant/currency.dart';
import 'package:siro_driver/constant/currency.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../../constant/finance_design_system.dart';
@@ -1,160 +0,0 @@
import 'package:tripz_driver/constant/currency.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:fl_chart/fl_chart.dart';
import '../../../../constant/finance_design_system.dart';
import '../../../../controller/home/statistics/statistics_controller.dart';
class MonthlyChartWidget extends StatelessWidget {
const MonthlyChartWidget({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<StatisticsController>(
builder: (sc) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: FinanceDesignSystem.cardColor,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4))
],
),
child:
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text('Monthly Report'.tr,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
const SizedBox(height: 16),
// Summary Row
Row(children: [
_summaryTile(
'Total Earnings'.tr,
'${sc.monthlyTotalEarnings.toStringAsFixed(0)} ${CurrencyHelper.currency}',
FinanceDesignSystem.successGreen),
const SizedBox(width: 12),
_summaryTile('Total Trips'.tr, '${sc.monthlyTotalTrips}',
FinanceDesignSystem.accentBlue),
const SizedBox(width: 12),
_summaryTile(
'Best Day'.tr, '${sc.bestDay}', const Color(0xFFFFD700)),
]),
const SizedBox(height: 20),
// Monthly Earnings Line Chart
SizedBox(
height: 200,
child: sc.monthlyEarnings.isEmpty
? Center(
child: Text('No data yet'.tr,
style: TextStyle(color: Colors.grey.shade400)))
: LineChart(LineChartData(
lineTouchData: LineTouchData(
enabled: true,
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (spots) => spots
.map((s) => LineTooltipItem(
'${s.y.toStringAsFixed(0)} ${CurrencyHelper.currency}',
const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12),
))
.toList(),
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (v) => FlLine(
color: FinanceDesignSystem.borderColor,
strokeWidth: 1),
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 5,
getTitlesWidget: (v, m) => Padding(
padding: const EdgeInsets.only(top: 8),
child: Text('${v.toInt()}',
style: TextStyle(
fontSize: 10,
color: FinanceDesignSystem.textSecondary)),
),
)),
leftTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(show: false),
lineBarsData: [
LineChartBarData(
spots: sc.monthlyEarnings
.map((e) =>
FlSpot(e.day.toDouble(), e.pricePerDay))
.toList(),
isCurved: true,
curveSmoothness: 0.3,
color: FinanceDesignSystem.accentBlue,
barWidth: 3,
dotData: FlDotData(
show: true,
getDotPainter: (s, p, d, i) => FlDotCirclePainter(
radius: 3,
color: FinanceDesignSystem.accentBlue,
strokeWidth: 1,
strokeColor: Colors.white),
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
FinanceDesignSystem.accentBlue.withOpacity(0.2),
FinanceDesignSystem.accentBlue.withOpacity(0.0)
],
),
),
),
],
)),
),
]),
);
},
);
}
Widget _summaryTile(String label, String value, Color color) {
return Expanded(
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.08),
borderRadius: BorderRadius.circular(12)),
child: Column(children: [
Text(value,
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w800, color: color)),
const SizedBox(height: 2),
Text(label,
style: TextStyle(
fontSize: 9, color: FinanceDesignSystem.textSecondary),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis),
]),
),
);
}
}
@@ -18,7 +18,9 @@ class StatSummaryCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
// ‏رأسي 8 لا 12: البطاقة داخل شبكة بارتفاع ثابت، و12 كانت تتجاوزه
// ‏قليلاً مع خط الرقم مقاس 22.
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: FinanceDesignSystem.cardColor,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
@@ -54,14 +56,22 @@ class StatSummaryCard extends StatelessWidget {
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
value,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
color: FinanceDesignSystem.primaryDark,
fontFamily: 'digit',
// ‏الرقم يتقلّص بدل أن يفيض: مبالغ الأرباح تطول بلا سقف
// ‏(٤ خانات + كسور + عملة) والبطاقة عرضها ثابت.
FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerStart,
child: Text(
value,
maxLines: 1,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w900,
color: FinanceDesignSystem.primaryDark,
fontFamily: 'digit',
),
),
),
Text(
@@ -1,86 +0,0 @@
import 'package:tripz_driver/constant/currency.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tripz_driver/controller/home/captin/home_captain_controller.dart';
import '../../../../constant/finance_design_system.dart';
class TodayChartWidget extends StatelessWidget {
TodayChartWidget({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<HomeCaptainController>(
builder: (hc) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: FinanceDesignSystem.cardColor,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4)),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Today Overview'.tr,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
const SizedBox(height: 4),
Text('Summary of your daily activity'.tr,
style: TextStyle(
fontSize: 11, color: FinanceDesignSystem.textSecondary)),
const SizedBox(height: 20),
_buildRow(
Icons.monetization_on_rounded,
'Earnings'.tr,
'${hc.totalMoneyToday} ${CurrencyHelper.currency}',
FinanceDesignSystem.successGreen),
const Divider(height: 24),
_buildRow(Icons.local_taxi_rounded, 'Rides'.tr, hc.countRideToday,
FinanceDesignSystem.accentBlue),
const Divider(height: 24),
Obx(() => _buildRow(Icons.timer_rounded, 'Online Duration'.tr,
hc.totalDurationDisplay.value, const Color(0xFFFF9800))),
const Divider(height: 24),
_buildRow(Icons.cancel_outlined, 'Refused'.tr, hc.countRefuse,
FinanceDesignSystem.dangerRed),
],
),
);
},
);
}
Widget _buildRow(IconData icon, String label, String value, Color color) {
return Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(10)),
child: Icon(icon, color: color, size: 20),
),
const SizedBox(width: 14),
Expanded(
child: Text(label,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: FinanceDesignSystem.primaryDark))),
Text(value,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
color: FinanceDesignSystem.primaryDark,
fontFamily: 'digit')),
],
);
}
}
@@ -1,173 +0,0 @@
import 'package:tripz_driver/constant/currency.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:fl_chart/fl_chart.dart';
import '../../../../constant/finance_design_system.dart';
import '../../../../controller/home/statistics/statistics_controller.dart';
class WeeklyChartWidget extends StatelessWidget {
const WeeklyChartWidget({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<StatisticsController>(
builder: (sc) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: FinanceDesignSystem.cardColor,
borderRadius: BorderRadius.circular(FinanceDesignSystem.cardRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
blurRadius: 10,
offset: const Offset(0, 4))
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Text('Weekly Earnings'.tr,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: FinanceDesignSystem.successGreen.withOpacity(0.1),
borderRadius: BorderRadius.circular(12)),
child: Text(
'${sc.weeklyEarnings.toStringAsFixed(0)} ${CurrencyHelper.currency}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.successGreen)),
),
]),
const SizedBox(height: 8),
Row(children: [
_miniStat(Icons.local_taxi_rounded, '${sc.weeklyTrips}',
'Rides'.tr, FinanceDesignSystem.accentBlue),
const SizedBox(width: 16),
_miniStat(
Icons.timer_rounded,
'${sc.weeklyHours.toStringAsFixed(1)}h',
'Hours'.tr,
const Color(0xFFFF9800)),
]),
const SizedBox(height: 20),
SizedBox(
height: 180,
child: sc.weeklyStats.isEmpty
? Center(
child: Text('No data yet'.tr,
style: TextStyle(color: Colors.grey.shade400)))
: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: _getMaxY(sc.weeklyStats),
barTouchData: BarTouchData(
enabled: true,
touchTooltipData: BarTouchTooltipData(
getTooltipItem: (group, gi, rod, ri) =>
BarTooltipItem(
'${rod.toY.toStringAsFixed(0)} ${CurrencyHelper.currency}',
const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12),
),
),
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (v, m) {
final idx = v.toInt();
if (idx >= 0 &&
idx < sc.weeklyStats.length) {
return Padding(
padding:
const EdgeInsets.only(top: 8),
child: Text(
sc.weeklyStats[idx].dayName.tr,
style: TextStyle(
fontSize: 10,
color: FinanceDesignSystem
.textSecondary)));
}
return const SizedBox.shrink();
})),
leftTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(show: false),
gridData: const FlGridData(show: false),
barGroups: List.generate(sc.weeklyStats.length, (i) {
final stat = sc.weeklyStats[i];
final isToday = i == sc.weeklyStats.length - 1;
return BarChartGroupData(x: i, barRods: [
BarChartRodData(
toY: stat.earnings,
width: 20,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(6)),
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: isToday
? [
FinanceDesignSystem.accentBlue,
const Color(0xFF82B1FF)
]
: [
FinanceDesignSystem.primaryDark
.withOpacity(0.6),
FinanceDesignSystem.primaryDark
.withOpacity(0.3)
],
),
),
]);
}),
),
),
),
],
),
);
},
);
}
Widget _miniStat(IconData icon, String value, String label, Color color) {
return Row(children: [
Icon(icon, size: 16, color: color),
const SizedBox(width: 4),
Text(value,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: FinanceDesignSystem.primaryDark)),
const SizedBox(width: 4),
Text(label,
style: TextStyle(
fontSize: 11, color: FinanceDesignSystem.textSecondary)),
]);
}
double _getMaxY(List<DayStat> stats) {
if (stats.isEmpty) return 100;
final max = stats.map((s) => s.earnings).reduce((a, b) => a > b ? a : b);
return max <= 0 ? 100 : max * 1.2;
}
}