تصميم عالمي جديد، وإصلاح الخرائط ومراقب السيرفرات

This commit is contained in:
Hamza-Ayed
2026-07-26 04:21:42 +03:00
parent f325ffce42
commit ca6cb7a3fe
10 changed files with 1367 additions and 673 deletions
+14 -16
View File
@@ -1,38 +1,36 @@
import 'package:flutter/material.dart';
class AppColor {
// --- Core Design Tokens ---
// Background & Surfaces
AppColor._();
// ─── Dark Mode Palette (static fallback / design tokens) ─────────────────
static const Color bg = Color(0xFF0A0A0B);
static const Color surface = Color(0xFF161618);
static const Color surfaceElevated = Color(0xFF222225);
static const Color surfaceGlass = Color(0xCC161618);
// Accents & Branding
static const Color accent = Color(0xFF6366F1); // Indigo / Violet
static const Color accentSoft = Color(0x266366F1); // 15% Opacity
static const Color accentBorder = Color(0x4D6366F1); // 30% Opacity
static const Color accent = Color(0xFF6366F1);
static const Color accentSoft = Color(0x266366F1);
static const Color accentBorder = Color(0x4D6366F1);
static const Color glow = Color(0xFF818CF8);
// Semantic / State Colors
static const Color danger = Color(0xFFEF4444);
static const Color dangerSoft = Color(0x26EF4444);
static const Color success = Color(0xFF10B981);
static const Color successSoft = Color(0x2610B981);
static const Color warning = Color(0xFFF59E0B);
static const Color warningSoft = Color(0x26F59E0B);
static const Color info = Color(0xFF3B82F6);
// Text & Content
static const Color infoSoft = Color(0x263B82F6);
static const Color textPrimary = Color(0xFFF3F4F6);
static const Color textSecondary = Color(0xFF9CA3AF);
static const Color textMuted = Color(0xFF6B7280);
// UI Elements
static const Color divider = Color(0xFF2D2D30);
static const Color cardShadow = Color(0x66000000);
// --- Legacy Mappings (for temporary compatibility) ---
// ─── Legacy Mappings ─────────────────────────────────────────────────────
static const Color primaryColor = bg;
static const Color secondaryColor = textPrimary;
static const Color accentColor = accent;
@@ -40,5 +38,5 @@ class AppColor {
static const Color greenColor = success;
static const Color blueColor = info;
static const Color yellowColor = warning;
static const Color deepPurpleAccent = accent; // Map to accent
static const Color deepPurpleAccent = accent;
}
+93 -3
View File
@@ -4,7 +4,9 @@ import 'package:google_fonts/google_fonts.dart';
import 'colors.dart';
class AppStyle {
// --- Typography ---
AppStyle._();
// ─── Static typography (dark mode defaults, backward compatible) ───────────
static TextStyle display = GoogleFonts.inter(
fontWeight: FontWeight.w800,
@@ -49,7 +51,91 @@ class AppStyle {
color: AppColor.accent,
);
// --- Decorations ---
// ─── Context-aware typography (adapts to theme) ───────────────────────────
static TextStyle displayFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.w800,
fontSize: 32,
color: Theme.of(context).colorScheme.onSurface,
letterSpacing: -1,
);
static TextStyle headTitleFor(BuildContext context) => GoogleFonts.cairo(
fontWeight: FontWeight.bold,
fontSize: 24,
color: Theme.of(context).colorScheme.onSurface,
);
static TextStyle titleFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.w600,
fontSize: 16,
color: Theme.of(context).colorScheme.onSurface,
);
static TextStyle subtitleFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.w500,
fontSize: 14,
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
static TextStyle bodyFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.normal,
fontSize: 14,
color: Theme.of(context).colorScheme.onSurface,
);
static TextStyle captionFor(BuildContext context) => GoogleFonts.inter(
fontWeight: FontWeight.w400,
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
);
static TextStyle numberFor(BuildContext context) => GoogleFonts.jetBrainsMono(
fontWeight: FontWeight.bold,
fontSize: 15,
color: Theme.of(context).colorScheme.primary,
);
// ─── Context-aware decorations ────────────────────────────────────────────
static BoxDecoration cardDecorationFor(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return BoxDecoration(
color: cs.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: cs.outline, width: 1),
boxShadow: [
BoxShadow(
color: cs.shadow.withValues(alpha: context.isDark ? 0.4 : 0.05),
blurRadius: 20,
offset: const Offset(0, 8),
),
],
);
}
static BoxDecoration elevatedCardFor(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: cs.primary.withValues(alpha: context.isDark ? 0.3 : 0.2),
width: 1,
),
);
}
static BoxDecoration glassDecorationFor(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return BoxDecoration(
color: cs.surface.withValues(alpha: context.isDark ? 0.8 : 0.9),
borderRadius: BorderRadius.circular(24),
border: Border.all(color: cs.outline, width: 1),
);
}
// ─── Legacy static decorations ────────────────────────────────────────────
static BoxDecoration cardDecoration = BoxDecoration(
color: AppColor.surface,
@@ -76,8 +162,12 @@ class AppStyle {
border: Border.all(color: AppColor.divider, width: 1),
);
// --- Legacy Mappings ---
// ─── Legacy Mappings ─────────────────────────────────────────────────────
static TextStyle headTitle2 = headTitle;
static BoxDecoration boxDecoration = cardDecoration;
static BoxDecoration boxDecoration1 = elevatedCard;
}
extension _Brightness on BuildContext {
bool get isDark => Theme.of(this).brightness == Brightness.dark;
}
+620
View File
@@ -0,0 +1,620 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'colors.dart';
class AppTheme {
AppTheme._();
// ─── Dark Theme (Premium SaaS) ─────────────────────────────────────────────
static ThemeData get darkTheme {
final darkScheme = ColorScheme.dark(
brightness: Brightness.dark,
primary: AppColor.accent,
onPrimary: Colors.white,
primaryContainer: AppColor.accentSoft,
onPrimaryContainer: AppColor.accent,
secondary: AppColor.glow,
onSecondary: Colors.white,
secondaryContainer: const Color(0x26818CF8),
onSecondaryContainer: AppColor.glow,
tertiary: AppColor.info,
onTertiary: Colors.white,
surface: AppColor.surface,
onSurface: AppColor.textPrimary,
surfaceContainerHighest: AppColor.surfaceElevated,
surfaceContainerHigh: const Color(0xFF1C1C1F),
surfaceContainerMedium: const Color(0xFF1E1E21),
surfaceContainerLow: const Color(0xFF222225),
surfaceContainer: AppColor.surface,
onSurfaceVariant: AppColor.textSecondary,
error: AppColor.danger,
onError: Colors.white,
errorContainer: AppColor.dangerSoft,
onErrorContainer: AppColor.danger,
outline: AppColor.divider,
outlineVariant: const Color(0xFF3D3D42),
shadow: Colors.black,
scrim: Colors.black54,
inverseSurface: AppColor.textPrimary,
onInverseSurface: AppColor.bg,
inversePrimary: AppColor.glow,
);
return ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
fontFamily: GoogleFonts.tajawal().fontFamily,
scaffoldBackgroundColor: AppColor.bg,
colorScheme: darkScheme,
dividerColor: AppColor.divider,
dividerTheme: const DividerThemeData(
color: AppColor.divider,
thickness: 1,
space: 1,
),
appBarTheme: AppBarTheme(
backgroundColor: AppColor.bg,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
titleTextStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
),
iconTheme: const IconThemeData(color: AppColor.textPrimary),
),
cardTheme: CardThemeData(
color: AppColor.surface,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(color: AppColor.divider, width: 1),
),
),
dialogTheme: DialogThemeData(
backgroundColor: AppColor.surface,
elevation: 24,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(color: AppColor.divider, width: 1),
),
titleTextStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
),
contentTextStyle: GoogleFonts.tajawal(
color: AppColor.textSecondary,
fontSize: 14,
),
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: AppColor.surface,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: AppColor.surfaceElevated,
contentTextStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 14,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
behavior: SnackBarBehavior.floating,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: AppColor.surfaceElevated,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColor.accent, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColor.danger, width: 1),
),
labelStyle: const TextStyle(color: AppColor.textSecondary),
hintStyle: const TextStyle(color: AppColor.textMuted),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.accent,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: AppColor.textPrimary,
side: const BorderSide(color: AppColor.divider),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: AppColor.accent,
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return AppColor.success;
return AppColor.textMuted;
}),
trackColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return AppColor.successSoft;
return AppColor.surfaceElevated;
}),
),
popupMenuTheme: PopupMenuThemeData(
color: AppColor.surface,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: AppColor.divider),
),
),
tooltipTheme: TooltipThemeData(
decoration: BoxDecoration(
color: AppColor.surfaceElevated,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: AppColor.divider),
),
textStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 12,
),
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: AppColor.accent,
foregroundColor: Colors.white,
elevation: 4,
),
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: AppColor.accent,
linearTrackColor: AppColor.surfaceElevated,
),
tabBarTheme: TabBarThemeData(
labelColor: AppColor.accent,
unselectedLabelColor: AppColor.textSecondary,
indicatorColor: AppColor.accent,
dividerColor: AppColor.divider,
),
chipTheme: ChipThemeData(
backgroundColor: AppColor.surfaceElevated,
labelStyle: GoogleFonts.tajawal(color: AppColor.textPrimary, fontSize: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(color: AppColor.divider),
),
),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.windows: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.macOS: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.linux: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.fuchsia: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
),
);
}
// ─── Light Theme (Premium SaaS) ────────────────────────────────────────────
static ThemeData get lightTheme {
const bgLight = Color(0xFFF8F9FC);
const surfaceLight = Color(0xFFFFFFFF);
const surfaceElevatedLight = Color(0xFFF1F3F9);
const dividerLight = Color(0xFFE2E5ED);
const textPrimaryLight = Color(0xFF111827);
const textSecondaryLight = Color(0xFF6B7280);
const textMutedLight = Color(0xFF9CA3AF);
const accentLight = Color(0xFF6366F1);
const accentSoftLight = Color(0x1A6366F1);
const accentBorderLight = Color(0x336366F1);
const glowLight = Color(0xFF4F46E5);
const dangerLight = Color(0xFFDC2626);
const dangerSoftLight = Color(0x1ADC2626);
const successLight = Color(0xFF059669);
const successSoftLight = Color(0x1A059669);
const warningLight = Color(0xFFD97706);
const infoLight = Color(0xFF2563EB);
final lightScheme = ColorScheme.light(
brightness: Brightness.light,
primary: accentLight,
onPrimary: Colors.white,
primaryContainer: accentSoftLight,
onPrimaryContainer: accentLight,
secondary: glowLight,
onSecondary: Colors.white,
secondaryContainer: const Color(0x1A4F46E5),
onSecondaryContainer: glowLight,
tertiary: infoLight,
onTertiary: Colors.white,
surface: surfaceLight,
onSurface: textPrimaryLight,
surfaceContainerHighest: surfaceElevatedLight,
surfaceContainerHigh: const Color(0xFFF5F6FA),
surfaceContainerMedium: const Color(0xFFF8F9FC),
surfaceContainerLow: const Color(0xFFFAFBFD),
surfaceContainer: surfaceLight,
onSurfaceVariant: textSecondaryLight,
error: dangerLight,
onError: Colors.white,
errorContainer: dangerSoftLight,
onErrorContainer: dangerLight,
outline: dividerLight,
outlineVariant: const Color(0xFFD1D5DB),
shadow: Colors.black26,
scrim: Colors.black38,
inverseSurface: textPrimaryLight,
onInverseSurface: surfaceLight,
inversePrimary: const Color(0xFFC7D2FE),
);
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
fontFamily: GoogleFonts.tajawal().fontFamily,
scaffoldBackgroundColor: bgLight,
colorScheme: lightScheme,
dividerColor: dividerLight,
dividerTheme: const DividerThemeData(
color: dividerLight,
thickness: 1,
space: 1,
),
appBarTheme: AppBarTheme(
backgroundColor: bgLight,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
titleTextStyle: GoogleFonts.tajawal(
color: textPrimaryLight,
fontSize: 18,
fontWeight: FontWeight.bold,
),
iconTheme: const IconThemeData(color: textPrimaryLight),
),
cardTheme: CardThemeData(
color: surfaceLight,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(color: dividerLight, width: 1),
),
),
dialogTheme: DialogThemeData(
backgroundColor: surfaceLight,
elevation: 24,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(color: dividerLight, width: 1),
),
titleTextStyle: GoogleFonts.tajawal(
color: textPrimaryLight,
fontSize: 18,
fontWeight: FontWeight.bold,
),
contentTextStyle: GoogleFonts.tajawal(
color: textSecondaryLight,
fontSize: 14,
),
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: surfaceLight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: surfaceElevatedLight,
contentTextStyle: GoogleFonts.tajawal(
color: textPrimaryLight,
fontSize: 14,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
behavior: SnackBarBehavior.floating,
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: surfaceElevatedLight,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: accentLight, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: dangerLight, width: 1),
),
labelStyle: const TextStyle(color: textSecondaryLight),
hintStyle: const TextStyle(color: textMutedLight),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: accentLight,
foregroundColor: Colors.white,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: textPrimaryLight,
side: const BorderSide(color: dividerLight),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: accentLight,
textStyle: GoogleFonts.tajawal(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return successLight;
return textMutedLight;
}),
trackColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) return successSoftLight;
return surfaceElevatedLight;
}),
),
popupMenuTheme: PopupMenuThemeData(
color: surfaceLight,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: dividerLight),
),
),
tooltipTheme: TooltipThemeData(
decoration: BoxDecoration(
color: surfaceElevatedLight,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: dividerLight),
),
textStyle: GoogleFonts.tajawal(
color: textPrimaryLight,
fontSize: 12,
),
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: accentLight,
foregroundColor: Colors.white,
elevation: 4,
),
progressIndicatorTheme: const ProgressIndicatorThemeData(
color: accentLight,
linearTrackColor: surfaceElevatedLight,
),
tabBarTheme: TabBarThemeData(
labelColor: accentLight,
unselectedLabelColor: textSecondaryLight,
indicatorColor: accentLight,
dividerColor: dividerLight,
),
chipTheme: ChipThemeData(
backgroundColor: surfaceElevatedLight,
labelStyle: GoogleFonts.tajawal(color: textPrimaryLight, fontSize: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: const BorderSide(color: dividerLight),
),
),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.windows: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.macOS: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.linux: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.fuchsia: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
},
),
);
}
}
// ─── Context Extensions for Theme Access ──────────────────────────────────────
extension ThemeExtensions on BuildContext {
ColorScheme get colors => Theme.of(this).colorScheme;
TextTheme get textStyles => Theme.of(this).textTheme;
Brightness get brightness => Theme.of(this).brightness;
bool get isDark => brightness == Brightness.dark;
}
// ─── Semantic Color Getters (light/dark adaptive) ─────────────────────────────
// These replace direct AppColor.* usage in views. They automatically adapt
// based on the current theme brightness.
class AppColors {
AppColors._();
static Color bg(BuildContext context) => context.isDark
? const Color(0xFF0A0A0B)
: const Color(0xFFF8F9FC);
static Color surface(BuildContext context) => context.isDark
? const Color(0xFF161618)
: const Color(0xFFFFFFFF);
static Color surfaceElevated(BuildContext context) => context.isDark
? const Color(0xFF222225)
: const Color(0xFFF1F3F9);
static Color surfaceGlass(BuildContext context) => context.isDark
? const Color(0xCC161618)
: const Color(0xCCFFFFFF);
static Color accent(BuildContext context) => context.isDark
? const Color(0xFF6366F1)
: const Color(0xFF6366F1);
static Color accentSoft(BuildContext context) => context.isDark
? const Color(0x266366F1)
: const Color(0x1A6366F1);
static Color accentBorder(BuildContext context) => context.isDark
? const Color(0x4D6366F1)
: const Color(0x336366F1);
static Color glow(BuildContext context) => context.isDark
? const Color(0xFF818CF8)
: const Color(0xFF4F46E5);
static Color danger(BuildContext context) => context.isDark
? const Color(0xFFEF4444)
: const Color(0xFFDC2626);
static Color dangerSoft(BuildContext context) => context.isDark
? const Color(0x26EF4444)
: const Color(0x1ADC2626);
static Color success(BuildContext context) => context.isDark
? const Color(0xFF10B981)
: const Color(0xFF059669);
static Color successSoft(BuildContext context) => context.isDark
? const Color(0x2610B981)
: const Color(0x1A059669);
static Color warning(BuildContext context) => context.isDark
? const Color(0xFFF59E0B)
: const Color(0xFFD97706);
static Color warningSoft(BuildContext context) => context.isDark
? const Color(0x26F59E0B)
: const Color(0x1AD97706);
static Color info(BuildContext context) => context.isDark
? const Color(0xFF3B82F6)
: const Color(0xFF2563EB);
static Color infoSoft(BuildContext context) => context.isDark
? const Color(0x263B82F6)
: const Color(0x1A2563EB);
static Color textPrimary(BuildContext context) => context.isDark
? const Color(0xFFF3F4F6)
: const Color(0xFF111827);
static Color textSecondary(BuildContext context) => context.isDark
? const Color(0xFF9CA3AF)
: const Color(0xFF6B7280);
static Color textMuted(BuildContext context) => context.isDark
? const Color(0xFF6B7280)
: const Color(0xFF9CA3AF);
static Color divider(BuildContext context) => context.isDark
? const Color(0xFF2D2D30)
: const Color(0xFFE2E5ED);
static Color cardShadow(BuildContext context) => context.isDark
? const Color(0x66000000)
: const Color(0x0D000000);
// Gradient helpers for charts
static LinearGradient accentGradient(BuildContext context) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
accent(context).withValues(alpha: 0.4),
accent(context).withValues(alpha: 0.0),
],
);
static LinearGradient successGradient(BuildContext context) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
success(context).withValues(alpha: 0.4),
success(context).withValues(alpha: 0.0),
],
);
static LinearGradient dangerGradient(BuildContext context) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
danger(context).withValues(alpha: 0.4),
danger(context).withValues(alpha: 0.0),
],
);
static LinearGradient infoGradient(BuildContext context) => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
info(context).withValues(alpha: 0.4),
info(context).withValues(alpha: 0.0),
],
);
}
+6 -30
View File
@@ -9,12 +9,11 @@ import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'constant/box_name.dart';
import 'constant/theme.dart';
import 'controller/firebase/firbase_messge.dart';
import 'controller/functions/encrypt_decrypt.dart';
import 'firebase_options.dart';
import 'models/db_sql.dart';
import 'package:google_fonts/google_fonts.dart';
import 'constant/colors.dart';
import 'routes.dart';
import 'binding/initial_binding.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
@@ -27,7 +26,7 @@ Future<void> backgroundMessageHandler(RemoteMessage message) async {
if (message.data.isNotEmpty && message.notification != null) {
FirebaseMessagesController().fireBaseTitles(message);
}
} //
}
DbSql sql = DbSql.instance;
@@ -40,7 +39,6 @@ void main() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// await FirebaseMessagesController().requestFirebaseMessagingPermission();
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
@@ -54,8 +52,7 @@ void main() async {
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
} // Enable Crashlytics collection
// FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;
}
runApp(const MainApp());
}
@@ -70,30 +67,9 @@ class MainApp extends StatelessWidget {
title: 'Siro Admin',
locale: const Locale('ar'),
fallbackLocale: const Locale('en'),
themeMode: ThemeMode.dark,
darkTheme: ThemeData(
fontFamily: GoogleFonts.tajawal().fontFamily,
brightness: Brightness.dark,
scaffoldBackgroundColor: AppColor.bg,
primaryColor: AppColor.accent,
colorScheme: const ColorScheme.dark(
primary: AppColor.accent,
secondary: AppColor.accent,
surface: AppColor.surface,
error: AppColor.danger,
),
dividerColor: AppColor.divider,
appBarTheme: AppBarTheme(
backgroundColor: AppColor.bg,
elevation: 0,
centerTitle: true,
titleTextStyle: GoogleFonts.tajawal(
color: AppColor.textPrimary,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
themeMode: ThemeMode.system,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
initialBinding: InitialBinding(),
initialRoute: box.read(BoxName.phoneVerified) == true ? "/" : "/login",
getPages: routes,
+177 -221
View File
@@ -8,7 +8,7 @@ import 'package:siro_admin/views/admin/drivers/driver_tracker_screen.dart';
import 'package:siro_admin/views/admin/quality/blacklist_page.dart';
import '../../constant/box_name.dart';
import '../../constant/colors.dart';
import '../../constant/theme.dart';
import '../../controller/admin/dashboard_controller.dart';
import '../../controller/admin/static_controller.dart';
import '../../controller/functions/crud.dart';
@@ -40,6 +40,7 @@ import 'package:siro_admin/views/widgets/responsive_layout.dart';
import 'package:siro_admin/views/widgets/web_sidebar.dart';
import '../transit/org_list_page.dart';
import 'package:siro_admin/views/widgets/glass_container.dart';
class AdminHomePage extends StatefulWidget {
const AdminHomePage({super.key});
@@ -57,21 +58,6 @@ class _AdminHomePageState extends State<AdminHomePage>
late DashboardController dashboardController;
String _searchQuery = '';
// ══════════════════ DESIGN TOKENS ══════════════════
// --- Unified with AppColor ---
static const Color _bg = AppColor.bg;
static const Color _surface = AppColor.surface;
static const Color _surfaceElevated = AppColor.surfaceElevated;
static const Color _accent = AppColor.accent;
static const Color _accentBorder = AppColor.accentBorder;
static const Color _danger = AppColor.danger;
static const Color _warning = AppColor.warning;
static const Color _info = AppColor.info;
static const Color _success = AppColor.success;
static const Color _textPrimary = AppColor.textPrimary;
static const Color _textSecondary = AppColor.textSecondary;
static const Color _divider = AppColor.divider;
@override
void initState() {
super.initState();
@@ -83,7 +69,6 @@ class _AdminHomePageState extends State<AdminHomePage>
final String role = box.read('admin_role')?.toString() ?? 'admin';
final String myPhone = box.read(BoxName.adminPhone)?.toString() ?? '';
// التحقق من الصلاحيات: إما عن طريق الدور أو عن طريق قائمة أرقام السوبر أدمن التقليدية
isSuperAdmin = (role == 'super_admin') ||
(myPhone == '201023248456' ||
myPhone == '963992952235' ||
@@ -101,36 +86,35 @@ class _AdminHomePageState extends State<AdminHomePage>
super.dispose();
}
// ══════════════════════════════════════════════════════════════
// BUILD
// ══════════════════════════════════════════════════════════════
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return ResponsiveLayout(
mobile: _buildMobileScaffold(),
mobile: _buildMobileScaffold(cs),
desktop: Scaffold(
backgroundColor: _bg,
backgroundColor: cs.surface,
body: Row(
children: [
const WebSidebar(selectedIndex: 0),
Expanded(child: _buildDesktopScaffold()),
Expanded(child: _buildDesktopScaffold(cs)),
],
),
),
);
}
Widget _buildMobileScaffold() {
Widget _buildMobileScaffold(ColorScheme cs) {
return Scaffold(
backgroundColor: _bg,
backgroundColor: cs.surface,
body: RefreshIndicator(
onRefresh: () async => await dashboardController.getDashBoard(),
color: _accent,
backgroundColor: _surface,
color: cs.primary,
backgroundColor: cs.surfaceContainerHighest,
child: GetBuilder<DashboardController>(
builder: (controller) {
if (controller.dashbord.isEmpty) {
return _buildLoadingState();
return _buildLoadingState(cs);
}
final data = controller.dashbord[0];
@@ -139,11 +123,11 @@ class _AdminHomePageState extends State<AdminHomePage>
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_buildSliverAppBar(controller),
_buildSearchBar(),
_buildSliverAppBar(controller, cs),
_buildSearchBar(cs),
if (_searchQuery.isEmpty) const DashboardV2Widget(),
if (_searchQuery.isEmpty)
_buildQuickStatsSection(data, controller),
_buildQuickStatsSection(data, controller, cs),
SliverPadding(
padding: const EdgeInsets.only(bottom: 60),
sliver: SliverList(
@@ -160,7 +144,7 @@ class _AdminHomePageState extends State<AdminHomePage>
child: SlideAnimation(
verticalOffset: 40.0,
child: FadeInAnimation(
child: _buildCategorySection(category),
child: _buildCategorySection(category, cs),
),
),
);
@@ -177,21 +161,21 @@ class _AdminHomePageState extends State<AdminHomePage>
);
}
Widget _buildDesktopScaffold() {
Widget _buildDesktopScaffold(ColorScheme cs) {
return Scaffold(
backgroundColor: _bg,
backgroundColor: cs.surface,
body: GetBuilder<DashboardController>(
builder: (controller) {
if (controller.dashbord.isEmpty) {
return _buildLoadingState();
return _buildLoadingState(cs);
}
final data = controller.dashbord[0];
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
_buildSliverAppBar(controller),
_buildSliverAppBar(controller, cs),
const DashboardV2Widget(),
_buildQuickStatsSection(data, controller),
_buildQuickStatsSection(data, controller, cs),
],
);
},
@@ -199,10 +183,7 @@ class _AdminHomePageState extends State<AdminHomePage>
);
}
// ══════════════════════════════════════════════════════════════
// LOADING STATE
// ══════════════════════════════════════════════════════════════
Widget _buildLoadingState() {
Widget _buildLoadingState(ColorScheme cs) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -213,15 +194,15 @@ class _AdminHomePageState extends State<AdminHomePage>
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [_accent.withOpacity(0.3), Colors.transparent],
colors: [cs.primary.withValues(alpha: 0.3), Colors.transparent],
),
),
child: const Center(
child: Center(
child: SizedBox(
width: 28,
height: 28,
child: CircularProgressIndicator(
color: _accent,
color: cs.primary,
strokeWidth: 2.5,
),
),
@@ -229,53 +210,55 @@ class _AdminHomePageState extends State<AdminHomePage>
),
const SizedBox(height: 16),
Text('جاري التحميل...',
style: TextStyle(color: _textSecondary, fontSize: 13)),
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13)),
],
),
);
}
// ══════════════════════════════════════════════════════════════
// SLIVER APP BAR
// ══════════════════════════════════════════════════════════════
Widget _buildSliverAppBar(DashboardController controller) {
Widget _buildSliverAppBar(DashboardController controller, ColorScheme cs) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return SliverAppBar(
expandedHeight: 130.0,
floating: true,
pinned: true,
backgroundColor: _bg,
backgroundColor: cs.surface,
elevation: 0,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Stack(
fit: StackFit.expand,
children: [
// Aurora gradient background
Container(
decoration: const BoxDecoration(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF0D2137),
Color(0xFF0D1117),
Color(0xFF0F1F1A),
],
colors: isDark
? [
cs.primary.withValues(alpha: 0.08),
cs.surface,
cs.tertiary.withValues(alpha: 0.05),
]
: [
cs.primary.withValues(alpha: 0.05),
cs.surface,
cs.tertiary.withValues(alpha: 0.03),
],
),
),
),
// Subtle glow orbs
Positioned(
top: -30,
left: -40,
child: _GlowOrb(color: _accent, size: 150, opacity: 0.08),
child: _GlowOrb(color: cs.primary, size: 150, opacity: 0.08),
),
Positioned(
top: -20,
right: -20,
child: _GlowOrb(color: _info, size: 120, opacity: 0.06),
child: _GlowOrb(color: cs.tertiary, size: 120, opacity: 0.06),
),
// Content
Align(
alignment: Alignment.bottomCenter,
child: Padding(
@@ -283,12 +266,12 @@ class _AdminHomePageState extends State<AdminHomePage>
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildLogo(),
_buildLogo(cs),
const SizedBox(height: 6),
Text(
isSuperAdmin ? 'Super Admin Panel' : 'Admin Panel',
style: TextStyle(
color: _textSecondary,
color: cs.onSurfaceVariant,
fontSize: 11,
letterSpacing: 1.5,
),
@@ -302,13 +285,13 @@ class _AdminHomePageState extends State<AdminHomePage>
),
actions: [
_buildHeaderAction(
Icons.refresh_rounded, () => controller.getDashBoard()),
Icons.refresh_rounded, () => controller.getDashBoard(), cs),
const SizedBox(width: 8),
],
);
}
Widget _buildLogo() {
Widget _buildLogo(ColorScheme cs) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -321,19 +304,20 @@ class _AdminHomePageState extends State<AdminHomePage>
borderRadius: BorderRadius.circular(12),
gradient: LinearGradient(
colors: [
_accent.withOpacity(0.3 + 0.1 * _pulseController.value),
_accent.withOpacity(0.1),
cs.primary.withValues(
alpha: 0.3 + 0.1 * _pulseController.value),
cs.primary.withValues(alpha: 0.1),
],
),
border: Border.all(
color:
_accent.withOpacity(0.4 + 0.2 * _pulseController.value),
color: cs.primary.withValues(
alpha: 0.4 + 0.2 * _pulseController.value),
width: 1,
),
),
child: const Icon(
child: Icon(
Icons.admin_panel_settings_rounded,
color: _accent,
color: cs.primary,
size: 18,
),
);
@@ -341,8 +325,8 @@ class _AdminHomePageState extends State<AdminHomePage>
),
const SizedBox(width: 10),
ShaderMask(
shaderCallback: (bounds) => const LinearGradient(
colors: [_accent, _info],
shaderCallback: (bounds) => LinearGradient(
colors: [cs.primary, cs.tertiary],
).createShader(bounds),
child: const Text(
'Siro Admin',
@@ -358,50 +342,48 @@ class _AdminHomePageState extends State<AdminHomePage>
);
}
Widget _buildHeaderAction(IconData icon, VoidCallback onTap) {
Widget _buildHeaderAction(IconData icon, VoidCallback onTap, ColorScheme cs) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: _surface,
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: _divider),
border: Border.all(color: cs.outline),
),
child: Icon(icon, color: _textSecondary, size: 18),
child: Icon(icon, color: cs.onSurfaceVariant, size: 18),
),
);
}
// ══════════════════════════════════════════════════════════════
// SEARCH BAR
// ══════════════════════════════════════════════════════════════
Widget _buildSearchBar() {
Widget _buildSearchBar(ColorScheme cs) {
final isFocused = _searchQuery.isNotEmpty;
return SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Container(
decoration: BoxDecoration(
color: _surface,
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: _searchQuery.isNotEmpty ? _accentBorder : _divider,
width: _searchQuery.isNotEmpty ? 1.5 : 1,
color: isFocused ? cs.primary.withValues(alpha: 0.5) : cs.outline,
width: isFocused ? 1.5 : 1,
),
),
child: TextField(
controller: _searchController,
onChanged: (val) => setState(() => _searchQuery = val),
style: const TextStyle(color: _textPrimary, fontSize: 14),
style: TextStyle(color: cs.onSurface, fontSize: 14),
decoration: InputDecoration(
hintText: 'ابحث عن خدمة أو ميزة...',
hintStyle: const TextStyle(color: _textSecondary, fontSize: 13),
hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
prefixIcon:
const Icon(Icons.search_rounded, color: _accent, size: 20),
Icon(Icons.search_rounded, color: cs.primary, size: 20),
suffixIcon: _searchQuery.isNotEmpty
? IconButton(
icon: Icon(Icons.close_rounded,
color: _textSecondary, size: 18),
color: cs.onSurfaceVariant, size: 18),
onPressed: () {
setState(() {
_searchQuery = '';
@@ -420,23 +402,21 @@ class _AdminHomePageState extends State<AdminHomePage>
);
}
// ══════════════════════════════════════════════════════════════
// QUICK STATS SECTION
// ══════════════════════════════════════════════════════════════
Widget _buildQuickStatsSection(dynamic data, DashboardController controller) {
Widget _buildQuickStatsSection(
dynamic data, DashboardController controller, ColorScheme cs) {
final highlights = [
_HighlightData(
'إجمالي الركاب', data['countPassengers'], Icons.group_rounded, _info),
'إجمالي الركاب', data['countPassengers'], Icons.group_rounded, cs.tertiary),
_HighlightData('إجمالي السائقين', data['countDriver'],
Icons.drive_eta_rounded, _warning),
Icons.drive_eta_rounded, cs.secondary),
_HighlightData('رحلات الشهر', data['countRideThisMonth'],
Icons.calendar_today_rounded, const Color(0xFFC792EA)),
Icons.calendar_today_rounded, cs.primary),
if (isSuperAdmin)
_HighlightData('المحفظة', _formatCurrency(data['seferWallet']),
Icons.account_balance_wallet_rounded, _accent),
Icons.account_balance_wallet_rounded, cs.primary),
];
final detailedStats = _getDetailedStats(data, controller);
final detailedStats = _getDetailedStats(data, controller, cs);
return SliverToBoxAdapter(
child: Padding(
@@ -444,7 +424,6 @@ class _AdminHomePageState extends State<AdminHomePage>
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Section label
Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 10),
child: Row(
@@ -453,14 +432,14 @@ class _AdminHomePageState extends State<AdminHomePage>
width: 3,
height: 14,
decoration: BoxDecoration(
color: _accent,
color: cs.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
const Text('نظرة عامة',
Text('نظرة عامة',
style: TextStyle(
color: _textSecondary,
color: cs.onSurfaceVariant,
fontSize: 11,
fontWeight: FontWeight.w600,
letterSpacing: 1.2,
@@ -468,8 +447,6 @@ class _AdminHomePageState extends State<AdminHomePage>
],
),
),
// Highlight Cards
SizedBox(
height: 108,
child: ListView.builder(
@@ -479,21 +456,18 @@ class _AdminHomePageState extends State<AdminHomePage>
itemBuilder: (ctx, i) => Padding(
padding: EdgeInsets.only(
right: i < highlights.length - 1 ? 10 : 0),
child: _buildHighlightCard(highlights[i]),
child: _buildHighlightCard(highlights[i], cs),
),
),
),
const SizedBox(height: 16),
// Detailed stats strip
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
decoration: BoxDecoration(
color: _surface,
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _divider),
border: Border.all(color: cs.outline),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
@@ -505,12 +479,12 @@ class _AdminHomePageState extends State<AdminHomePage>
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildDetailStatItem(stat),
_buildDetailStatItem(stat, cs),
if (i < detailedStats.length - 1)
Container(
width: 1,
height: 36,
color: _divider,
color: cs.outline,
),
],
);
@@ -520,7 +494,6 @@ class _AdminHomePageState extends State<AdminHomePage>
),
),
),
const SizedBox(height: 8),
],
),
@@ -528,14 +501,14 @@ class _AdminHomePageState extends State<AdminHomePage>
);
}
Widget _buildHighlightCard(_HighlightData h) {
Widget _buildHighlightCard(_HighlightData h, ColorScheme cs) {
return GlassContainer(
width: 155,
padding: const EdgeInsets.all(16),
borderRadius: 16,
gradientColors: [
h.color.withOpacity(0.18),
h.color.withOpacity(0.03),
h.color.withValues(alpha: 0.18),
h.color.withValues(alpha: 0.03),
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -546,7 +519,7 @@ class _AdminHomePageState extends State<AdminHomePage>
Container(
padding: const EdgeInsets.all(7),
decoration: BoxDecoration(
color: h.color.withOpacity(0.12),
color: h.color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(9),
),
child: Icon(h.icon, color: h.color, size: 16),
@@ -556,7 +529,7 @@ class _AdminHomePageState extends State<AdminHomePage>
height: 6,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: h.color.withOpacity(0.6),
color: h.color.withValues(alpha: 0.6),
),
),
],
@@ -564,8 +537,8 @@ class _AdminHomePageState extends State<AdminHomePage>
const Spacer(),
Text(
h.value.toString(),
style: const TextStyle(
color: _textPrimary,
style: TextStyle(
color: cs.onSurface,
fontSize: 20,
fontWeight: FontWeight.w700,
height: 1.1,
@@ -575,8 +548,8 @@ class _AdminHomePageState extends State<AdminHomePage>
const SizedBox(height: 3),
Text(
h.label,
style: const TextStyle(
color: _textSecondary,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
fontWeight: FontWeight.w500,
),
@@ -587,7 +560,7 @@ class _AdminHomePageState extends State<AdminHomePage>
);
}
Widget _buildDetailStatItem(Map<String, dynamic> stat) {
Widget _buildDetailStatItem(Map<String, dynamic> stat, ColorScheme cs) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
child: Column(
@@ -598,8 +571,8 @@ class _AdminHomePageState extends State<AdminHomePage>
const SizedBox(height: 6),
Text(
stat['value'].toString(),
style: const TextStyle(
color: _textPrimary,
style: TextStyle(
color: cs.onSurface,
fontSize: 14,
fontWeight: FontWeight.bold,
height: 1,
@@ -608,8 +581,8 @@ class _AdminHomePageState extends State<AdminHomePage>
const SizedBox(height: 3),
Text(
stat['title'] as String,
style: const TextStyle(
color: _textSecondary,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 9,
fontWeight: FontWeight.w500,
),
@@ -619,10 +592,7 @@ class _AdminHomePageState extends State<AdminHomePage>
);
}
// ══════════════════════════════════════════════════════════════
// CATEGORY SECTION
// ══════════════════════════════════════════════════════════════
Widget _buildCategorySection(ActionCategory category) {
Widget _buildCategorySection(ActionCategory category, ColorScheme cs) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -634,15 +604,15 @@ class _AdminHomePageState extends State<AdminHomePage>
width: 3,
height: 14,
decoration: BoxDecoration(
color: _accent,
color: cs.primary,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
category.title,
style: const TextStyle(
color: _textPrimary,
style: TextStyle(
color: cs.onSurface,
fontSize: 15,
fontWeight: FontWeight.w600,
letterSpacing: 0.3,
@@ -650,13 +620,13 @@ class _AdminHomePageState extends State<AdminHomePage>
),
const SizedBox(width: 10),
Expanded(
child: Container(height: 1, color: _divider),
child: Container(height: 1, color: cs.outline),
),
const SizedBox(width: 8),
Text(
'${category.items.length}',
style: const TextStyle(
color: _textSecondary,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
fontWeight: FontWeight.w500,
),
@@ -676,26 +646,26 @@ class _AdminHomePageState extends State<AdminHomePage>
),
itemCount: category.items.length,
itemBuilder: (context, index) =>
_buildActionItem(category.items[index]),
_buildActionItem(category.items[index], cs),
),
const SizedBox(height: 8),
],
);
}
Widget _buildActionItem(ActionItem item) {
Widget _buildActionItem(ActionItem item, ColorScheme cs) {
return Material(
color: Colors.transparent,
child: InkWell(
onTap: item.onPressed,
borderRadius: BorderRadius.circular(16),
splashColor: item.color.withOpacity(0.1),
highlightColor: item.color.withOpacity(0.05),
splashColor: item.color.withValues(alpha: 0.1),
highlightColor: item.color.withValues(alpha: 0.05),
child: Container(
decoration: BoxDecoration(
color: _surface,
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: _divider),
border: Border.all(color: cs.outline),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -707,15 +677,16 @@ class _AdminHomePageState extends State<AdminHomePage>
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
item.color.withOpacity(0.20),
item.color.withOpacity(0.08),
item.color.withValues(alpha: 0.20),
item.color.withValues(alpha: 0.08),
],
),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: item.color.withOpacity(0.25)),
border:
Border.all(color: item.color.withValues(alpha: 0.25)),
boxShadow: [
BoxShadow(
color: item.color.withOpacity(0.15),
color: item.color.withValues(alpha: 0.15),
blurRadius: 10,
offset: const Offset(0, 3),
),
@@ -731,8 +702,8 @@ class _AdminHomePageState extends State<AdminHomePage>
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: _textPrimary,
style: TextStyle(
color: cs.onSurface,
fontSize: 11,
fontWeight: FontWeight.w500,
height: 1.3,
@@ -746,9 +717,6 @@ class _AdminHomePageState extends State<AdminHomePage>
);
}
// ══════════════════════════════════════════════════════════════
// DATA HELPERS
// ══════════════════════════════════════════════════════════════
List<ActionCategory> _getFilteredCategories() {
final all = _getAllActionCategories();
if (_searchQuery.isEmpty) return all;
@@ -772,31 +740,31 @@ class _AdminHomePageState extends State<AdminHomePage>
ActionCategory(
title: 'المستخدمين',
items: [
ActionItem('الركاب', Icons.people_outline_rounded, _info,
ActionItem('الركاب', Icons.people_outline_rounded, const Color(0xFF3B82F6),
() => Get.to(() => Passengrs())),
ActionItem('السائقون', Icons.drive_eta_rounded, _warning,
ActionItem('السائقون', Icons.drive_eta_rounded, const Color(0xFFF59E0B),
() => Get.to(() => CaptainsPage())),
ActionItem('المراقب', Icons.track_changes_rounded, _danger,
ActionItem('المراقب', Icons.track_changes_rounded, const Color(0xFFEF4444),
() => Get.to(() => SiroTrackerScreen())),
],
),
ActionCategory(
title: 'مواصلاتي',
items: [
ActionItem('المؤسسات', Icons.directions_bus_filled_rounded, _accent,
ActionItem('المؤسسات', Icons.directions_bus_filled_rounded, const Color(0xFF6366F1),
() => Get.to(() => const TransitOrgListPage())),
],
),
ActionCategory(
title: 'إدارة النظام الجديد',
items: [
ActionItem('أكواد الخصم', Icons.confirmation_number_rounded, _accent,
ActionItem('أكواد الخصم', Icons.confirmation_number_rounded, const Color(0xFF6366F1),
() => Get.toNamed('/promo')),
ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, _warning,
ActionItem('تعديل الأسعار', Icons.settings_suggest_rounded, const Color(0xFFF59E0B),
() => Get.toNamed('/kazan')),
ActionItem('الشكاوى', Icons.report_problem_rounded, _danger,
ActionItem('الشكاوى', Icons.report_problem_rounded, const Color(0xFFEF4444),
() => Get.toNamed('/complaints')),
ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, _info,
ActionItem('مراجعة الوثائق', Icons.assignment_ind_rounded, const Color(0xFF3B82F6),
() => Get.toNamed('/driver-docs')),
ActionItem('استخبارات السوشيال', Icons.insights_rounded, const Color(0xFFC792EA),
() => Get.toNamed('/social-intelligence')),
@@ -813,11 +781,11 @@ class _AdminHomePageState extends State<AdminHomePage>
Icons.remove_red_eye_rounded,
const Color(0xFFC792EA),
() => Get.to(() => RideMonitorScreen())),
ActionItem('الإحصائيات', Icons.bar_chart_rounded, _accent, () async {
ActionItem('الإحصائيات', Icons.bar_chart_rounded, const Color(0xFF6366F1), () async {
await Get.put(StaticController()).getAll();
Get.to(() => const StaticDash());
}),
ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, _info,
ActionItem('التحليلات المتقدمة', Icons.analytics_rounded, const Color(0xFF3B82F6),
() => Get.to(() => const AdvancedAnalyticsPage())),
ActionItem('لوحة البيانات التفاعلية', Icons.dashboard_customize_rounded,
const Color(0xFF00CEC9),
@@ -827,7 +795,7 @@ class _AdminHomePageState extends State<AdminHomePage>
ActionCategory(
title: 'الجودة والدعم',
items: [
ActionItem('القائمة السوداء', Icons.block_flipped, _danger,
ActionItem('القائمة السوداء', Icons.block_flipped, const Color(0xFFEF4444),
() => Get.to(() => const BlacklistPage())),
],
),
@@ -836,16 +804,16 @@ class _AdminHomePageState extends State<AdminHomePage>
title: 'المالية والإدارة',
items: [
ActionItem('الإدارة المالية V2', Icons.account_balance_rounded,
_accent, () => Get.to(() => const FinancialV2Page())),
ActionItem('المحفظة', Icons.account_balance_wallet_rounded, _accent,
const Color(0xFF6366F1), () => Get.to(() => const FinancialV2Page())),
ActionItem('المحفظة', Icons.account_balance_wallet_rounded, const Color(0xFF6366F1),
() => Get.to(() => Wallet())),
ActionItem('هدية 300', Icons.card_giftcard_rounded, _warning,
ActionItem('هدية 300', Icons.card_giftcard_rounded, const Color(0xFFF59E0B),
() => Get.to(() => DriverGiftCheckPage())),
ActionItem('الفواتير', Icons.receipt_long_rounded,
const Color(0xFF80CBC4), () => Get.to(() => InvoiceListPage())),
ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFFB0BEC5),
const Color(0xFF10B981), () => Get.to(() => InvoiceListPage())),
ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFF9CA3AF),
() => Get.to(() => EmployeePage())),
ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, _accent,
ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, const Color(0xFF6366F1),
() => Get.to(() => const PendingAdminsPage())),
],
),
@@ -854,11 +822,11 @@ class _AdminHomePageState extends State<AdminHomePage>
title: 'النظام والتواصل',
items: [
ActionItem('سجل العمليات', Icons.admin_panel_settings_rounded,
_danger, () => Get.to(() => const AuditLogsPage())),
const Color(0xFFEF4444), () => Get.to(() => const AuditLogsPage())),
ActionItem('واتساب جماعي', Icons.message_rounded,
const Color(0xFF4CAF50), () => _showWhatsAppDialog(context)),
const Color(0xFF25D366), () => _showWhatsAppDialog(context)),
ActionItem('أتمتة التسويق', Icons.campaign_rounded,
const Color(0xFF80CBC4), () => Get.toNamed('/marketing')),
const Color(0xFF10B981), () => Get.toNamed('/marketing')),
ActionItem(
'إشعار سائقين',
Icons.notifications_active_rounded,
@@ -871,21 +839,17 @@ class _AdminHomePageState extends State<AdminHomePage>
const Color(0xFFF06292),
() => Get.put(NotificationController())
.sendNotificationPassengers()),
ActionItem('تسجيل سائق', Icons.person_add_rounded, _info,
ActionItem('تسجيل سائق', Icons.person_add_rounded, const Color(0xFF3B82F6),
() => Get.to(() => DriversPendingPage())),
ActionItem(
'تحديث التطبيق',
Icons.system_update_rounded,
const Color(0xFFA1887F),
const Color(0xFF9CA3AF),
() => Get.to(() => PackageUpdateScreen())),
ActionItem('مراقب السيرفر', Icons.dns_rounded, _accent,
ActionItem('مراقب السيرفر', Icons.dns_rounded, const Color(0xFF6366F1),
() => Get.to(() => ServerMonitorPage())),
ActionItem('سجل الأخطاء', Icons.error_outline_rounded, _danger,
ActionItem('سجل الأخطاء', Icons.error_outline_rounded, const Color(0xFFEF4444),
() => Get.to(() => ErrorListPage())),
// ActionItem('encrypt fp', Icons.error_outline_rounded, _danger,
// () => Get.to(() => FingerprintMigrationTool())),
// ActionItem('encrypt fp drivers', Icons.error_outline_rounded,
// _danger, () => Get.to(() => DriverFingerprintMigrationTool())),
ActionItem(
'أداة التشفير',
Icons.lock_rounded,
@@ -902,13 +866,13 @@ class _AdminHomePageState extends State<AdminHomePage>
ActionItem(
'إضافة مدير',
Icons.admin_panel_settings_rounded,
_accent,
const Color(0xFF6366F1),
() => Get.to(() => const AddStaffPage(role: 'admin')),
),
ActionItem(
'إضافة خدمة عملاء',
Icons.support_agent_rounded,
_info,
const Color(0xFF3B82F6),
() => Get.to(() => const AddStaffPage(role: 'service')),
),
],
@@ -917,44 +881,44 @@ class _AdminHomePageState extends State<AdminHomePage>
}
List<Map<String, dynamic>> _getDetailedStats(
dynamic data, DashboardController controller) {
dynamic data, DashboardController controller, ColorScheme cs) {
return [
if (isSuperAdmin)
{
'title': 'رصيد الرسائل',
'value': controller.creditSMS,
'icon': Icons.sms_rounded,
'color': _info,
'color': cs.tertiary,
},
{
'title': 'مكتملة',
'value': data['completed_rides'],
'icon': Icons.check_circle_rounded,
'color': _success,
'color': const Color(0xFF10B981),
},
{
'title': 'ملغاة',
'value': data['cancelled_rides'],
'icon': Icons.cancel_rounded,
'color': _danger,
'color': const Color(0xFFEF4444),
},
{
'title': 'مدفوعات',
'value': _formatCurrency(data['payments']),
'icon': Icons.attach_money_rounded,
'color': _warning,
'color': const Color(0xFFF59E0B),
},
{
'title': 'Comfort',
'value': data['comfort'],
'icon': Icons.chair_rounded,
'color': const Color(0xFF80CBC4),
'color': const Color(0xFF10B981),
},
{
'title': 'Speed',
'value': data['speed'],
'icon': Icons.flash_on_rounded,
'color': const Color(0xFFFFD54F),
'color': const Color(0xFFFBBF24),
},
{
'title': 'Lady',
@@ -965,23 +929,22 @@ class _AdminHomePageState extends State<AdminHomePage>
];
}
// ══════════════════════════════════════════════════════════════
// WHATSAPP DIALOG
// ══════════════════════════════════════════════════════════════
void _showWhatsAppDialog(BuildContext context) {
final cs = Theme.of(context).colorScheme;
Get.dialog(
Dialog(
backgroundColor: Colors.transparent,
child: Container(
decoration: BoxDecoration(
color: _surfaceElevated,
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: _divider),
boxShadow: const [
border: Border.all(color: cs.outline),
boxShadow: [
BoxShadow(
color: Colors.black45,
color: cs.shadow.withValues(alpha: 0.3),
blurRadius: 30,
offset: Offset(0, 12),
offset: const Offset(0, 12),
),
],
),
@@ -992,45 +955,44 @@ class _AdminHomePageState extends State<AdminHomePage>
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF4CAF50).withAlpha(30), // ~0.12 opacity
color: const Color(0xFF25D366).withValues(alpha: 0.12),
shape: BoxShape.circle,
border: Border.all(
color: const Color(0xFF4CAF50)
.withAlpha(64)), // ~0.25 opacity
color: const Color(0xFF25D366).withValues(alpha: 0.25)),
),
child: const Icon(Icons.message_rounded,
color: Color(0xFF4CAF50), size: 28),
color: Color(0xFF25D366), size: 28),
),
const SizedBox(height: 16),
const Text(
Text(
'إرسال واتساب جماعي',
style: TextStyle(
color: _textPrimary,
color: cs.onSurface,
fontSize: 17,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
const Text(
Text(
'سيتم إرسال الرسالة لجميع السائقين',
style: TextStyle(color: _textSecondary, fontSize: 11),
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 11),
),
const SizedBox(height: 20),
Container(
decoration: BoxDecoration(
color: _bg,
color: cs.surface,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: _divider),
border: Border.all(color: cs.outline),
),
child: TextField(
controller: _messageController,
maxLines: 4,
style: const TextStyle(color: _textPrimary, fontSize: 13),
decoration: const InputDecoration(
style: TextStyle(color: cs.onSurface, fontSize: 13),
decoration: InputDecoration(
hintText: 'اكتب رسالتك هنا...',
hintStyle: TextStyle(color: _textSecondary, fontSize: 12),
hintStyle: TextStyle(color: cs.onSurfaceVariant, fontSize: 12),
border: InputBorder.none,
contentPadding: EdgeInsets.all(14),
contentPadding: const EdgeInsets.all(14),
),
),
),
@@ -1044,12 +1006,12 @@ class _AdminHomePageState extends State<AdminHomePage>
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: const BorderSide(color: _divider),
side: BorderSide(color: cs.outline),
),
),
child: const Text(
child: Text(
'إلغاء',
style: TextStyle(color: _textSecondary, fontSize: 13),
style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
),
),
),
@@ -1060,7 +1022,7 @@ class _AdminHomePageState extends State<AdminHomePage>
label:
const Text('إرسال', style: TextStyle(fontSize: 13)),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF4CAF50),
backgroundColor: const Color(0xFF25D366),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
@@ -1108,9 +1070,6 @@ class _AdminHomePageState extends State<AdminHomePage>
}
}
// ══════════════════════════════════════════════════════════════
// HELPER WIDGETS
// ══════════════════════════════════════════════════════════════
class _GlowOrb extends StatelessWidget {
final Color color;
final double size;
@@ -1127,16 +1086,13 @@ class _GlowOrb extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [color.withOpacity(opacity), Colors.transparent],
colors: [color.withValues(alpha: opacity), Colors.transparent],
),
),
);
}
}
// ══════════════════════════════════════════════════════════════
// DATA CLASSES
// ══════════════════════════════════════════════════════════════
class _HighlightData {
final String label;
final dynamic value;
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/constant/theme.dart';
import 'package:siro_admin/controller/admin/dashboard_v2_controller.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:siro_admin/views/widgets/glass_container.dart';
@@ -10,15 +10,17 @@ class DashboardV2Widget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return GetBuilder<DashboardV2Controller>(
init: DashboardV2Controller(),
builder: (ctrl) {
if (ctrl.isLoading) {
return const SliverToBoxAdapter(
return SliverToBoxAdapter(
child: SizedBox(
height: 150,
child: Center(
child: CircularProgressIndicator(color: AppColor.accent),
child: CircularProgressIndicator(color: cs.primary),
),
),
);
@@ -28,16 +30,13 @@ class DashboardV2Widget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 1. Real-time stats
_buildSectionTitle('مركز العمليات الحي (Real-time)'),
_buildRealtimeStats(ctrl.realtimeData),
_buildSectionTitle('مركز العمليات الحي (Real-time)', cs),
_buildRealtimeStats(ctrl.realtimeData, cs),
const SizedBox(height: 20),
// 2. Smart Alerts
if (ctrl.smartAlerts.isNotEmpty) ...[
_buildSectionTitle('التنبيهات الذكية (${ctrl.smartAlerts.length})'),
_buildSmartAlerts(ctrl.smartAlerts),
_buildSectionTitle(
'التنبيهات الذكية (${ctrl.smartAlerts.length})', cs),
_buildSmartAlerts(ctrl.smartAlerts, cs),
const SizedBox(height: 10),
]
],
@@ -47,7 +46,7 @@ class DashboardV2Widget extends StatelessWidget {
);
}
Widget _buildSectionTitle(String title) {
Widget _buildSectionTitle(String title, ColorScheme cs) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 10),
child: Row(
@@ -56,15 +55,15 @@ class DashboardV2Widget extends StatelessWidget {
width: 3,
height: 14,
decoration: BoxDecoration(
color: AppColor.danger, // Distinct color
color: cs.error,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
title,
style: const TextStyle(
color: AppColor.textSecondary,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 12,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
@@ -75,43 +74,43 @@ class DashboardV2Widget extends StatelessWidget {
);
}
Widget _buildRealtimeStats(Map<String, dynamic> data) {
Widget _buildRealtimeStats(Map<String, dynamic> data, ColorScheme cs) {
final stats = [
{
'title': 'رحلات نشطة',
'value': data['active_rides']?.toString() ?? '0',
'icon': Icons.directions_car_rounded,
'color': AppColor.info,
'color': cs.tertiary,
},
{
'title': 'سائقون أونلاين',
'value': data['online_drivers']?.toString() ?? '0',
'icon': Icons.wifi_tethering,
'color': AppColor.success,
'color': const Color(0xFF10B981),
},
{
'title': 'إيرادات اليوم',
'value': '${data['revenue_today'] ?? 0}',
'icon': Icons.monetization_on_rounded,
'color': AppColor.warning,
'color': const Color(0xFFF59E0B),
},
{
'title': 'إيرادات الأمس',
'value': '${data['revenue_yesterday'] ?? 0}',
'icon': Icons.history_rounded,
'color': Colors.grey,
'color': cs.onSurfaceVariant,
},
{
'title': 'شكاوى مفتوحة',
'value': data['new_complaints']?.toString() ?? '0',
'icon': Icons.warning_rounded,
'color': AppColor.danger,
'color': cs.error,
},
{
'title': 'رخص تنتهي قريبًا',
'value': data['expiring_licenses']?.toString() ?? '0',
'icon': Icons.sd_card_alert_rounded,
'color': Colors.orangeAccent,
'color': const Color(0xFFF97316),
},
];
@@ -125,22 +124,22 @@ class DashboardV2Widget extends StatelessWidget {
final stat = stats[i];
return Padding(
padding: const EdgeInsets.only(right: 10),
child: _buildStatCard(stat),
child: _buildStatCard(stat, cs),
);
},
),
);
}
Widget _buildStatCard(Map<String, dynamic> stat) {
Widget _buildStatCard(Map<String, dynamic> stat, ColorScheme cs) {
final color = stat['color'] as Color;
return GlassContainer(
width: 150,
padding: const EdgeInsets.all(16),
borderRadius: 16,
gradientColors: [
color.withOpacity(0.15),
color.withOpacity(0.05),
color.withValues(alpha: 0.15),
color.withValues(alpha: 0.05),
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -151,36 +150,34 @@ class DashboardV2Widget extends StatelessWidget {
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: color.withOpacity(0.15),
color: color.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(8),
),
child: Icon(stat['icon'] as IconData, color: color, size: 16),
),
const Spacer(),
// Pulsing indicator for active things
if (stat['title'] == 'رحلات نشطة' || stat['title'] == 'سائقون أونلاين')
if (stat['title'] == 'رحلات نشطة' ||
stat['title'] == 'سائقون أونلاين')
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
boxShadow: [
BoxShadow(
color: color.withOpacity(0.5),
blurRadius: 4,
spreadRadius: 1,
)
]
),
),
width: 8,
height: 8,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.5),
blurRadius: 4,
spreadRadius: 1,
)
])),
],
),
const Spacer(),
Text(
stat['value'].toString(),
style: const TextStyle(
color: AppColor.textPrimary,
style: TextStyle(
color: cs.onSurface,
fontSize: 20,
fontWeight: FontWeight.bold,
),
@@ -188,8 +185,8 @@ class DashboardV2Widget extends StatelessWidget {
const SizedBox(height: 2),
Text(
stat['title'].toString(),
style: const TextStyle(
color: AppColor.textSecondary,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
fontWeight: FontWeight.w500,
),
@@ -201,12 +198,12 @@ class DashboardV2Widget extends StatelessWidget {
);
}
Widget _buildSmartAlerts(List<dynamic> alerts) {
Widget _buildSmartAlerts(List<dynamic> alerts, ColorScheme cs) {
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: alerts.length > 5 ? 5 : alerts.length, // Show top 5
itemCount: alerts.length > 5 ? 5 : alerts.length,
itemBuilder: (context, index) {
final alert = alerts[index];
return AnimationConfiguration.staggeredList(
@@ -215,7 +212,7 @@ class DashboardV2Widget extends StatelessWidget {
child: SlideAnimation(
verticalOffset: 20.0,
child: FadeInAnimation(
child: _buildAlertItem(alert),
child: _buildAlertItem(alert, cs),
),
),
);
@@ -223,17 +220,17 @@ class DashboardV2Widget extends StatelessWidget {
);
}
Widget _buildAlertItem(Map<String, dynamic> alert) {
Widget _buildAlertItem(Map<String, dynamic> alert, ColorScheme cs) {
Color getSeverityColor(String severity) {
switch (severity) {
case 'high':
return AppColor.danger;
return cs.error;
case 'medium':
return AppColor.warning;
return const Color(0xFFF59E0B);
case 'warning':
return Colors.orangeAccent;
return const Color(0xFFF97316);
default:
return AppColor.info;
return cs.tertiary;
}
}
@@ -257,12 +254,12 @@ class DashboardV2Widget extends StatelessWidget {
margin: const EdgeInsets.only(bottom: 10),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: AppColor.surface,
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withOpacity(0.4)),
border: Border.all(color: color.withValues(alpha: 0.4)),
boxShadow: [
BoxShadow(
color: color.withOpacity(0.05),
color: color.withValues(alpha: 0.05),
blurRadius: 5,
offset: const Offset(0, 2),
)
@@ -274,7 +271,7 @@ class DashboardV2Widget extends StatelessWidget {
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: color, size: 20),
@@ -286,8 +283,8 @@ class DashboardV2Widget extends StatelessWidget {
children: [
Text(
alert['title'] ?? '',
style: const TextStyle(
color: AppColor.textPrimary,
style: TextStyle(
color: cs.onSurface,
fontSize: 13,
fontWeight: FontWeight.bold,
),
@@ -295,8 +292,8 @@ class DashboardV2Widget extends StatelessWidget {
const SizedBox(height: 4),
Text(
alert['description'] ?? '',
style: const TextStyle(
color: AppColor.textSecondary,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 11,
),
maxLines: 2,
@@ -314,14 +311,14 @@ class DashboardV2Widget extends StatelessWidget {
alert['date'] != null
? alert['date'].toString().split(' ')[0]
: '',
style: const TextStyle(
color: AppColor.textSecondary,
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 10,
),
),
const SizedBox(height: 10),
Icon(Icons.arrow_forward_ios_rounded,
color: AppColor.textSecondary, size: 12),
color: cs.onSurfaceVariant, size: 12),
],
),
],
File diff suppressed because it is too large Load Diff
@@ -10,6 +10,9 @@ class GlassContainer extends StatelessWidget {
final double borderRadius;
final List<Color>? gradientColors;
final double blur;
final double opacity;
final bool showBorder;
final Color? borderColor;
const GlassContainer({
super.key,
@@ -20,15 +23,35 @@ class GlassContainer extends StatelessWidget {
this.margin,
this.borderRadius = 16.0,
this.gradientColors,
this.blur = 10.0,
this.blur = 20.0,
this.opacity = 0.1,
this.showBorder = true,
this.borderColor,
});
@override
Widget build(BuildContext context) {
final defaultGradient = [
Colors.white.withOpacity(0.1),
Colors.white.withOpacity(0.05),
];
final isDark = Theme.of(context).brightness == Brightness.dark;
final cs = Theme.of(context).colorScheme;
final defaultGradient = isDark
? [
Colors.white.withValues(alpha: opacity),
Colors.white.withValues(alpha: opacity * 0.5),
]
: [
Colors.white.withValues(alpha: 0.7),
Colors.white.withValues(alpha: 0.4),
];
final effectiveBorderColor = borderColor ??
(isDark
? Colors.white.withValues(alpha: 0.15)
: Colors.white.withValues(alpha: 0.5));
final shadowColor = isDark
? Colors.black.withValues(alpha: 0.3)
: cs.shadow.withValues(alpha: 0.08);
return Container(
width: width,
@@ -38,10 +61,11 @@ class GlassContainer extends StatelessWidget {
borderRadius: BorderRadius.circular(borderRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
spreadRadius: 1,
)
color: shadowColor,
blurRadius: 24,
spreadRadius: 0,
offset: const Offset(0, 8),
),
],
),
child: ClipRRect(
@@ -52,10 +76,9 @@ class GlassContainer extends StatelessWidget {
padding: padding,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1.5,
),
border: showBorder
? Border.all(color: effectiveBorderColor, width: 1)
: null,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
+116 -48
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:siro_admin/constant/colors.dart';
import 'package:siro_admin/constant/theme.dart';
import 'package:siro_admin/main.dart';
import 'package:siro_admin/constant/box_name.dart';
import 'package:siro_admin/views/admin/captain/captain.dart';
@@ -40,6 +40,8 @@ class WebSidebar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
final String myPhone = box.read(BoxName.adminPhone)?.toString() ?? '';
final String role = box.read('admin_role')?.toString() ?? 'admin';
final bool isSuperAdmin = (role == 'super_admin') ||
@@ -49,11 +51,11 @@ class WebSidebar extends StatelessWidget {
return Container(
width: 270,
color: AppColor.surface,
color: cs.surface,
child: Column(
children: [
_buildLogo(),
const Divider(color: AppColor.divider, height: 1),
_buildLogo(context),
Divider(color: cs.outline, height: 1),
Expanded(
child: ListView(
padding: const EdgeInsets.symmetric(vertical: 12),
@@ -68,8 +70,7 @@ class WebSidebar extends StatelessWidget {
},
),
// ─── التحليلات والإحصائيات ──────────────────────────────
_buildSectionHeader('التحليلات والإحصائيات'),
_buildSectionHeader(context, 'التحليلات والإحصائيات'),
_buildNavItem(
context,
title: 'التحليلات الحية',
@@ -95,8 +96,7 @@ class WebSidebar extends StatelessWidget {
},
),
// ─── إدارة الكباتن والركاب ──────────────────────────────
_buildSectionHeader('الكباتن والركاب'),
_buildSectionHeader(context, 'الكباتن والركاب'),
_buildNavItem(
context,
title: 'إدارة الركاب',
@@ -140,8 +140,7 @@ class WebSidebar extends StatelessWidget {
onTap: () => Get.to(() => const BlacklistPage()),
),
// ─── الرحلات والمراقبة ──────────────────────────────
_buildSectionHeader('الرحلات والتتبع'),
_buildSectionHeader(context, 'الرحلات والتتبع'),
_buildNavItem(
context,
title: 'شاشة الرحلات',
@@ -157,13 +156,6 @@ class WebSidebar extends StatelessWidget {
index: 6,
onTap: () => Get.to(() => RideMonitorScreen()),
),
// _buildNavItem(
// context,
// title: 'البحث عن رحلة',
// icon: Icons.search_rounded,
// index: 107,
// onTap: () => Get.to(() => RideLookupPage()),
// ),
_buildNavItem(
context,
title: 'تتبع الكباتن (المراقب)',
@@ -172,8 +164,7 @@ class WebSidebar extends StatelessWidget {
onTap: () => Get.to(() => SiroTrackerScreen()),
),
// ─── مواصلاتي والشركات ──────────────────────────────
_buildSectionHeader('نظام مواصلاتي'),
_buildSectionHeader(context, 'نظام مواصلاتي'),
_buildNavItem(
context,
title: 'إدارة المؤسسات والشركات',
@@ -182,8 +173,7 @@ class WebSidebar extends StatelessWidget {
onTap: () => Get.to(() => const TransitOrgListPage()),
),
// ─── المالية والاشتراكات ──────────────────────────────
_buildSectionHeader('المالية والأسعار'),
_buildSectionHeader(context, 'المالية والأسعار'),
_buildNavItem(
context,
title: 'التقرير المالي (V2)',
@@ -227,11 +217,10 @@ class WebSidebar extends StatelessWidget {
onTap: () => Get.toNamed('/promo'),
),
// ─── التسويق والدعم الفني ──────────────────────────────
_buildSectionHeader('التسويق والدعم'),
_buildSectionHeader(context, 'التسويق والدعم'),
_buildNavItem(
context,
title: 'حركية التسويق والـ AI',
title: 'حركات التسويق والـ AI',
icon: Icons.campaign_rounded,
index: 112,
onTap: () => Get.toNamed('/marketing'),
@@ -258,9 +247,8 @@ class WebSidebar extends StatelessWidget {
onTap: () => Get.toNamed('/complaints'),
),
// ─── إدارة النظام والأمان ──────────────────────────────
if (isSuperAdmin) ...[
_buildSectionHeader('إدارة النظام والأمان'),
_buildSectionHeader(context, 'إدارة النظام والأمان'),
_buildNavItem(
context,
title: 'مراقبة السيرفرات',
@@ -307,14 +295,16 @@ class WebSidebar extends StatelessWidget {
],
),
),
const Divider(color: AppColor.divider, height: 1),
Divider(color: cs.outline, height: 1),
_buildThemeToggle(context),
_buildLogoutBtn(context),
],
),
);
}
Widget _buildLogo() {
Widget _buildLogo(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
child: Row(
@@ -322,12 +312,12 @@ class WebSidebar extends StatelessWidget {
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColor.accent.withOpacity(0.1),
color: cs.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColor.accent.withOpacity(0.3)),
border: Border.all(color: cs.primary.withValues(alpha: 0.3)),
),
child: const Icon(Icons.admin_panel_settings_rounded,
color: AppColor.accent, size: 28),
child: Icon(Icons.admin_panel_settings_rounded,
color: cs.primary, size: 28),
),
const SizedBox(width: 12),
Column(
@@ -336,7 +326,7 @@ class WebSidebar extends StatelessWidget {
Text(
'SIRO ADMIN',
style: TextStyle(
color: AppColor.textPrimary,
color: cs.onSurface,
fontWeight: FontWeight.bold,
fontSize: 16,
letterSpacing: 1.1,
@@ -346,7 +336,7 @@ class WebSidebar extends StatelessWidget {
Text(
'Web Dashboard',
style: TextStyle(
color: AppColor.textSecondary,
color: cs.onSurfaceVariant,
fontSize: 11,
),
),
@@ -357,13 +347,14 @@ class WebSidebar extends StatelessWidget {
);
}
Widget _buildSectionHeader(String title) {
Widget _buildSectionHeader(BuildContext context, String title) {
final cs = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(right: 20, left: 20, top: 18, bottom: 6),
child: Text(
title,
style: TextStyle(
color: AppColor.accent.withOpacity(0.85),
color: cs.primary.withValues(alpha: 0.85),
fontSize: 11,
fontWeight: FontWeight.bold,
letterSpacing: 0.5,
@@ -379,6 +370,7 @@ class WebSidebar extends StatelessWidget {
required int index,
required VoidCallback onTap,
}) {
final cs = Theme.of(context).colorScheme;
final bool isSelected = selectedIndex == index;
return Padding(
@@ -388,16 +380,16 @@ class WebSidebar extends StatelessWidget {
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(10),
hoverColor: AppColor.accent.withOpacity(0.08),
hoverColor: cs.primary.withValues(alpha: 0.08),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: isSelected
? AppColor.accent.withOpacity(0.15)
? cs.primary.withValues(alpha: 0.15)
: Colors.transparent,
borderRadius: BorderRadius.circular(10),
border: isSelected
? Border.all(color: AppColor.accent.withOpacity(0.4))
? Border.all(color: cs.primary.withValues(alpha: 0.4))
: null,
),
child: Row(
@@ -405,15 +397,14 @@ class WebSidebar extends StatelessWidget {
Icon(
icon,
size: 20,
color: isSelected ? AppColor.accent : AppColor.textSecondary,
color: isSelected ? cs.primary : cs.onSurfaceVariant,
),
const SizedBox(width: 14),
Expanded(
child: Text(
title,
style: TextStyle(
color:
isSelected ? AppColor.accent : AppColor.textPrimary,
color: isSelected ? cs.primary : cs.onSurface,
fontSize: 13.5,
fontWeight:
isSelected ? FontWeight.bold : FontWeight.w500,
@@ -428,7 +419,84 @@ class WebSidebar extends StatelessWidget {
);
}
Widget _buildThemeToggle(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
final newMode = isDark ? ThemeMode.light : ThemeMode.dark;
Get.changeThemeMode(newMode);
},
borderRadius: BorderRadius.circular(10),
hoverColor: cs.primary.withValues(alpha: 0.08),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: cs.surfaceContainerHighest,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: cs.outline),
),
child: Row(
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: Icon(
isDark ? Icons.light_mode_rounded : Icons.dark_mode_rounded,
key: ValueKey(isDark),
size: 20,
color: isDark ? const Color(0xFFFBBF24) : cs.primary,
),
),
const SizedBox(width: 14),
Expanded(
child: Text(
isDark ? 'الوضع الفاتح' : 'الوضع الداكن',
style: TextStyle(
color: cs.onSurface,
fontSize: 13.5,
fontWeight: FontWeight.w500,
),
),
),
AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: 44,
height: 24,
decoration: BoxDecoration(
color: isDark
? cs.primary.withValues(alpha: 0.3)
: cs.outline,
borderRadius: BorderRadius.circular(12),
),
child: AnimatedAlign(
duration: const Duration(milliseconds: 300),
alignment: isDark ? Alignment.centerRight : Alignment.centerLeft,
child: Container(
width: 20,
height: 20,
margin: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
color: isDark ? cs.primary : cs.onSurface,
shape: BoxShape.circle,
),
),
),
),
],
),
),
),
),
);
}
Widget _buildLogoutBtn(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Container(
padding: const EdgeInsets.all(16),
child: InkWell(
@@ -440,18 +508,18 @@ class WebSidebar extends StatelessWidget {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
color: cs.error.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.red.withOpacity(0.2)),
border: Border.all(color: cs.error.withValues(alpha: 0.2)),
),
child: const Row(
child: Row(
children: [
Icon(Icons.logout_rounded, color: Colors.redAccent, size: 20),
SizedBox(width: 14),
Icon(Icons.logout_rounded, color: cs.error, size: 20),
const SizedBox(width: 14),
Text(
'تسجيل الخروج',
style: TextStyle(
color: Colors.redAccent,
color: cs.error,
fontSize: 13.5,
fontWeight: FontWeight.bold,
),