تصميم عالمي جديد، وإصلاح الخرائط ومراقب السيرفرات
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user