From 51ea446ae9f7af7c124d8676046a21053776da36 Mon Sep 17 00:00:00 2001 From: 240405 <240405@epvc.pt> Date: Sun, 17 May 2026 14:02:42 +0100 Subject: [PATCH] =?UTF-8?q?Modifica=C3=A7=C3=A3o=20nos=20modos,=20corre?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20textos=20cortados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/theme/app_colors.dart | 153 +++--- lib/core/theme/app_theme.dart | 64 ++- lib/core/theme/app_theme_extension.dart | 106 ++++ .../presentation/widgets/chat_input.dart | 23 +- .../presentation/widgets/message_bubble.dart | 47 +- .../auth/presentation/pages/login_page.dart | 17 +- .../pages/role_selection_page.dart | 81 +-- .../auth/presentation/pages/signup_page.dart | 19 +- .../pages/student_dashboard_page.dart | 23 +- .../pages/teacher_dashboard_page.dart | 23 +- .../widgets/dashboard_action_card.dart | 308 +++++++++++ .../widgets/profile_section_widget.dart | 8 +- .../widgets/progress_hero_widget.dart | 9 +- .../widgets/quick_access_widget.dart | 317 ++--------- .../widgets/student_classes_list_widget.dart | 6 +- .../teacher_analytics_preview_widget.dart | 11 +- .../widgets/teacher_classes_list_widget.dart | 4 +- .../widgets/teacher_hero_widget.dart | 13 +- .../widgets/teacher_quick_actions_widget.dart | 517 +++++------------- .../pages/teacher_materials_page.dart | 48 +- .../presentation/pages/quiz_list_page.dart | 4 +- .../presentation/pages/teacher_quiz_page.dart | 3 +- .../presentation/pages/settings_page.dart | 20 +- .../presentation/pages/splash_page.dart | 15 +- 24 files changed, 906 insertions(+), 933 deletions(-) create mode 100644 lib/core/theme/app_theme_extension.dart create mode 100644 lib/features/dashboard/presentation/widgets/dashboard_action_card.dart diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index c98b34a..1bc825a 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -1,111 +1,90 @@ import 'package:flutter/material.dart'; -/// EPVC School Color Palette - New Color Scheme with Light/Dark Mode Support +/// EPVC School Color Palette - Light/Dark Mode Support class AppColors { - // Primary Brand Colors (same for both modes) - static const Color primaryTeal = Color( - 0xFF82C9BD, - ); // Main teal color - PRIMARY - static const Color primaryOrange = Color( - 0xFFF68D2D, - ); // Accent orange - SECONDARY + // Primary Brand Colors (light mode) + static const Color primaryTeal = Color(0xFF82C9BD); + static const Color primaryOrange = Color(0xFFF68D2D); - // Gradient Colors (same for both modes) - static const Color gradientStart = Color(0xFF82C9BD); // Teal gradient start - static const Color gradientEnd = Color( - 0xFF6AB8A8, - ); // Darker teal gradient end + // Gradient Colors (light mode) + static const Color gradientStart = Color(0xFF82C9BD); + static const Color gradientEnd = Color(0xFF6AB8A8); - // Secondary Colors (same for both modes) - static const Color secondaryTeal = Color(0xFF6AB8A8); // Darker teal - static const Color accentTeal = Color(0xFF5AA69A); // Lighter teal accent - static const Color lightOrange = Color(0xFFF7A960); // Lighter orange + // Secondary Colors (light mode) + static const Color secondaryTeal = Color(0xFF6AB8A8); + static const Color accentTeal = Color(0xFF5AA69A); + static const Color lightOrange = Color(0xFFF7A960); - // Status Colors (same for both modes) - static const Color success = Color(0xFF10B981); // Green for success - static const Color warning = Color(0xFFF59E0B); // Amber for warnings - static const Color error = Color(0xFFEF4444); // Red for errors - static const Color info = Color(0xFF3B82F6); // Blue for info + // Status Colors (shared) + static const Color success = Color(0xFF10B981); + static const Color warning = Color(0xFFF59E0B); + static const Color error = Color(0xFFEF4444); + static const Color info = Color(0xFF3B82F6); - // Legacy compatibility (for existing code) - @deprecated - static const Color primaryBlue = primaryTeal; // Map old primaryBlue to new primaryTeal + @Deprecated('Use AppColors.primaryTeal') + static const Color primaryBlue = primaryTeal; +} + +/// Brand colors tuned for dark mode (lower luminance, same hue family). +class DarkBrandColors { + static const Color primaryTeal = Color(0xFF4D8F84); + static const Color primaryOrange = Color(0xFFC47A2A); + static const Color gradientStart = Color(0xFF3D7A70); + static const Color gradientEnd = Color(0xFF2F635C); + static const Color secondaryTeal = Color(0xFF3D7A70); + static const Color accentTeal = Color(0xFF356B62); } /// Light Mode Colors class LightColors { - // Neutral Colors - static const Color background = Color(0xFFF8F9FA); // Light gray background - static const Color surface = Color(0xFFFFFFFF); // White surfaces - static const Color cardBackground = Color(0xFFFFFFFF); // White cards + static const Color background = Color(0xFFF8F9FA); + static const Color surface = Color(0xFFFFFFFF); + static const Color surfaceVariant = Color(0xFFF3F4F6); + static const Color cardBackground = Color(0xFFFFFFFF); - // Text Colors - static const Color textPrimary = Color(0xFF1A1A1A); // Primary text - static const Color textSecondary = Color(0xFF6B7280); // Secondary text - static const Color textHint = Color(0xFF9CA3AF); // Hint text + static const Color textPrimary = Color(0xFF1A1A1A); + static const Color textSecondary = Color(0xFF6B7280); + static const Color textHint = Color(0xFF9CA3AF); - // Interactive Colors - static const Color buttonPrimary = - AppColors.primaryTeal; // Primary button (teal) - static const Color buttonAccent = - AppColors.primaryOrange; // Accent button (orange) - static const Color buttonSecondary = Color(0xFFE5E7EB); // Secondary button - static const Color iconActive = AppColors.primaryTeal; // Active icons (teal) - static const Color iconInactive = Color(0xFF9CA3AF); // Inactive icons + static const Color buttonPrimary = AppColors.primaryTeal; + static const Color buttonAccent = AppColors.primaryOrange; + static const Color buttonSecondary = Color(0xFFE5E7EB); + static const Color iconActive = AppColors.primaryTeal; + static const Color iconInactive = Color(0xFF9CA3AF); - // Chat Specific Colors - static const Color chatBubbleStudent = - AppColors.primaryTeal; // Student messages (teal) - static const Color chatBubbleAI = Color(0xFFF3F4F6); // AI messages - static const Color chatInputBackground = Color( - 0xFFF8F9FA, - ); // Input background - static const Color chatSendButton = - AppColors.primaryTeal; // Send button (teal) + static const Color chatBubbleStudent = AppColors.primaryTeal; + static const Color chatBubbleAI = Color(0xFFF3F4F6); + static const Color chatInputBackground = Color(0xFFF8F9FA); + static const Color chatSendButton = AppColors.primaryTeal; - // Border Colors - static const Color border = Color(0xFFE2E8F0); // Border color - static const Color divider = Color(0xFFE5E7EB); // Divider color - - // Overlay Colors - static const Color overlay = Color(0x80000000); // Overlay color + static const Color border = Color(0xFFE2E8F0); + static const Color divider = Color(0xFFE5E7EB); + static const Color overlay = Color(0x80000000); } /// Dark Mode Colors class DarkColors { - // Neutral Colors - static const Color background = Color(0xFF1F2937); // Dark background - static const Color surface = Color(0xFF374151); // Dark surface - static const Color cardBackground = Color(0xFF374151); // Dark cards + static const Color background = Color(0xFF0F1218); + static const Color surface = Color(0xFF1A2332); + static const Color surfaceVariant = Color(0xFF243044); + static const Color cardBackground = Color(0xFF1A2332); - // Text Colors - static const Color textPrimary = Color(0xFFF9FAFB); // Dark primary text - static const Color textSecondary = Color(0xFFD1D5DB); // Dark secondary text - static const Color textHint = Color(0xFF9CA3AF); // Hint text + static const Color textPrimary = Color(0xFFF9FAFB); + static const Color textSecondary = Color(0xFFD1D5DB); + static const Color textHint = Color(0xFF9CA3AF); - // Interactive Colors - static const Color buttonPrimary = - AppColors.primaryTeal; // Primary button (teal) - static const Color buttonAccent = - AppColors.primaryOrange; // Accent button (orange) - static const Color buttonSecondary = Color(0xFF4B5563); // Secondary button - static const Color iconActive = AppColors.primaryTeal; // Active icons (teal) - static const Color iconInactive = Color(0xFF6B7280); // Inactive icons + static const Color buttonPrimary = DarkBrandColors.primaryTeal; + static const Color buttonAccent = DarkBrandColors.primaryOrange; + static const Color buttonSecondary = Color(0xFF2D3A4D); + static const Color iconActive = DarkBrandColors.primaryTeal; + static const Color iconInactive = Color(0xFF6B7280); - // Chat Specific Colors - static const Color chatBubbleStudent = - AppColors.primaryTeal; // Student messages (teal) - static const Color chatBubbleAI = Color(0xFF4B5563); // AI messages (darker) - static const Color chatInputBackground = Color( - 0xFF374151, - ); // Input background - static const Color chatSendButton = - AppColors.primaryTeal; // Send button (teal) + static const Color chatBubbleStudent = DarkBrandColors.primaryTeal; + static const Color chatBubbleAI = Color(0xFF243044); + static const Color chatInputBackground = Color(0xFF1A2332); + static const Color chatSendButton = DarkBrandColors.primaryTeal; - // Border Colors - static const Color border = Color(0xFF4B5563); // Border color - static const Color divider = Color(0xFF4B5563); // Divider color - - // Overlay Colors - static const Color overlay = Color(0x80000000); // Overlay color + static const Color border = Color(0xFF2D3A4D); + static const Color divider = Color(0xFF2D3A4D); + static const Color overlay = Color(0x80000000); } diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index a4456a8..74183bc 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'app_colors.dart'; +import 'app_theme_extension.dart'; /// Application Theme Configuration class AppTheme { @@ -12,11 +13,18 @@ class AppTheme { seedColor: AppColors.primaryTeal, brightness: Brightness.light, primary: AppColors.primaryTeal, + onPrimary: Colors.white, secondary: AppColors.primaryOrange, + onSecondary: Colors.white, surface: LightColors.surface, + onSurface: LightColors.textPrimary, + onSurfaceVariant: LightColors.textSecondary, + surfaceContainerHighest: LightColors.surfaceVariant, background: LightColors.background, error: AppColors.error, ), + scaffoldBackgroundColor: LightColors.background, + extensions: [AppThemeExtras.light], // App Bar Theme appBarTheme: const AppBarTheme( @@ -256,17 +264,22 @@ class AppTheme { return ThemeData( useMaterial3: true, brightness: Brightness.dark, - colorScheme: ColorScheme.fromSeed( - seedColor: AppColors.primaryTeal, - brightness: Brightness.dark, - primary: AppColors.primaryTeal, - secondary: AppColors.primaryOrange, + colorScheme: ColorScheme.dark( + primary: DarkBrandColors.primaryTeal, + onPrimary: Colors.white, + secondary: DarkBrandColors.primaryOrange, + onSecondary: Colors.white, surface: DarkColors.surface, + onSurface: DarkColors.textPrimary, + onSurfaceVariant: DarkColors.textSecondary, + surfaceContainerHighest: DarkColors.surfaceVariant, + surfaceContainerLow: DarkColors.background, background: DarkColors.background, error: AppColors.error, + outline: DarkColors.border, ), - - // Dark mode specific overrides would go here + scaffoldBackgroundColor: DarkColors.background, + extensions: [AppThemeExtras.dark], appBarTheme: const AppBarTheme( backgroundColor: DarkColors.surface, foregroundColor: DarkColors.textPrimary, @@ -294,7 +307,7 @@ class AppTheme { backgroundColor: DarkColors.buttonPrimary, foregroundColor: Colors.white, elevation: 2, - shadowColor: AppColors.primaryTeal.withOpacity(0.3), + shadowColor: DarkBrandColors.primaryTeal.withOpacity(0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -306,8 +319,10 @@ class AppTheme { // Outlined Button Theme for Dark Mode outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( - foregroundColor: AppColors.primaryTeal, - side: BorderSide(color: AppColors.primaryTeal.withOpacity(0.3)), + foregroundColor: DarkBrandColors.primaryTeal, + side: BorderSide( + color: DarkBrandColors.primaryTeal.withOpacity(0.3), + ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -319,7 +334,7 @@ class AppTheme { // Text Button Theme for Dark Mode textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( - foregroundColor: AppColors.primaryTeal, + foregroundColor: DarkBrandColors.primaryTeal, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), @@ -332,15 +347,22 @@ class AppTheme { fillColor: DarkColors.surface, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: AppColors.primaryTeal.withOpacity(0.3)), + borderSide: BorderSide( + color: DarkBrandColors.primaryTeal.withOpacity(0.3), + ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: AppColors.primaryTeal.withOpacity(0.3)), + borderSide: BorderSide( + color: DarkBrandColors.primaryTeal.withOpacity(0.3), + ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: AppColors.primaryTeal, width: 2), + borderSide: const BorderSide( + color: DarkBrandColors.primaryTeal, + width: 2, + ), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), @@ -363,9 +385,9 @@ class AppTheme { // Text Field Theme for Dark Mode textSelectionTheme: TextSelectionThemeData( - cursorColor: AppColors.primaryTeal, - selectionColor: AppColors.primaryTeal.withOpacity(0.3), - selectionHandleColor: AppColors.primaryTeal, + cursorColor: DarkBrandColors.primaryTeal, + selectionColor: DarkBrandColors.primaryTeal.withOpacity(0.3), + selectionHandleColor: DarkBrandColors.primaryTeal, ), textTheme: const TextTheme( @@ -449,7 +471,7 @@ class AppTheme { // Bottom Navigation Bar Theme for Dark Mode bottomNavigationBarTheme: const BottomNavigationBarThemeData( backgroundColor: DarkColors.surface, - selectedItemColor: AppColors.primaryTeal, + selectedItemColor: DarkBrandColors.primaryTeal, unselectedItemColor: DarkColors.iconInactive, type: BottomNavigationBarType.fixed, elevation: 8, @@ -465,7 +487,7 @@ class AppTheme { // Floating Action Button Theme for Dark Mode floatingActionButtonTheme: FloatingActionButtonThemeData( - backgroundColor: AppColors.primaryTeal, + backgroundColor: DarkBrandColors.primaryTeal, foregroundColor: Colors.white, elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), @@ -483,7 +505,7 @@ class AppTheme { // Progress Indicator Theme for Dark Mode progressIndicatorTheme: const ProgressIndicatorThemeData( - color: AppColors.primaryTeal, + color: DarkBrandColors.primaryTeal, linearTrackColor: DarkColors.buttonSecondary, circularTrackColor: DarkColors.buttonSecondary, ), @@ -491,7 +513,7 @@ class AppTheme { // Chip Theme for Dark Mode chipTheme: ChipThemeData( backgroundColor: DarkColors.buttonSecondary, - selectedColor: AppColors.primaryTeal.withOpacity(0.1), + selectedColor: DarkBrandColors.primaryTeal.withOpacity(0.1), disabledColor: DarkColors.buttonSecondary.withOpacity(0.5), labelStyle: const TextStyle(color: DarkColors.textPrimary), secondaryLabelStyle: const TextStyle(color: DarkColors.textPrimary), diff --git a/lib/core/theme/app_theme_extension.dart b/lib/core/theme/app_theme_extension.dart new file mode 100644 index 0000000..2a4c912 --- /dev/null +++ b/lib/core/theme/app_theme_extension.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; + +import 'app_colors.dart'; + +/// Theme extension for gradients and colors not covered by [ColorScheme]. +@immutable +class AppThemeExtras extends ThemeExtension { + const AppThemeExtras({ + required this.dashboardBackgroundGradient, + required this.dashboardGradientStops, + required this.heroProgressStart, + required this.heroProgressEnd, + required this.actionCardGradientStart, + required this.actionCardGradientEnd, + required this.authBackgroundGradient, + required this.dashboardHeaderTextColor, + }); + + final List dashboardBackgroundGradient; + final List dashboardGradientStops; + final Color heroProgressStart; + final Color heroProgressEnd; + final Color actionCardGradientStart; + final Color actionCardGradientEnd; + final List authBackgroundGradient; + final Color dashboardHeaderTextColor; + + static final AppThemeExtras light = AppThemeExtras( + dashboardBackgroundGradient: [ + AppColors.primaryTeal, + AppColors.primaryTeal.withValues(alpha: 0.8), + AppColors.primaryOrange, + LightColors.background, + ], + dashboardGradientStops: [0.0, 0.2, 0.6, 1.0], + heroProgressStart: Colors.white, + heroProgressEnd: Color(0xFFF8F9FA), + actionCardGradientStart: AppColors.primaryTeal, + actionCardGradientEnd: AppColors.gradientEnd, + authBackgroundGradient: [ + const Color(0xFFD4E8E8), + const Color(0xFFE8D4C0), + const Color(0xFFD8E0E8), + ], + dashboardHeaderTextColor: Colors.white, + ); + + static final AppThemeExtras dark = AppThemeExtras( + dashboardBackgroundGradient: [ + DarkColors.surfaceVariant, + DarkColors.surface, + DarkColors.background, + DarkColors.background, + ], + dashboardGradientStops: [0.0, 0.25, 0.55, 1.0], + heroProgressStart: Colors.white.withValues(alpha: 0.9), + heroProgressEnd: Colors.white.withValues(alpha: 0.55), + actionCardGradientStart: DarkBrandColors.gradientStart, + actionCardGradientEnd: DarkBrandColors.gradientEnd, + authBackgroundGradient: [ + DarkColors.surfaceVariant, + DarkColors.surface, + DarkColors.background, + ], + dashboardHeaderTextColor: DarkColors.textPrimary, + ); + + static AppThemeExtras of(BuildContext context) { + return Theme.of(context).extension() ?? light; + } + + @override + AppThemeExtras copyWith({ + List? dashboardBackgroundGradient, + List? dashboardGradientStops, + Color? heroProgressStart, + Color? heroProgressEnd, + Color? actionCardGradientStart, + Color? actionCardGradientEnd, + List? authBackgroundGradient, + Color? dashboardHeaderTextColor, + }) { + return AppThemeExtras( + dashboardBackgroundGradient: + dashboardBackgroundGradient ?? this.dashboardBackgroundGradient, + dashboardGradientStops: + dashboardGradientStops ?? this.dashboardGradientStops, + heroProgressStart: heroProgressStart ?? this.heroProgressStart, + heroProgressEnd: heroProgressEnd ?? this.heroProgressEnd, + actionCardGradientStart: + actionCardGradientStart ?? this.actionCardGradientStart, + actionCardGradientEnd: + actionCardGradientEnd ?? this.actionCardGradientEnd, + authBackgroundGradient: + authBackgroundGradient ?? this.authBackgroundGradient, + dashboardHeaderTextColor: + dashboardHeaderTextColor ?? this.dashboardHeaderTextColor, + ); + } + + @override + AppThemeExtras lerp(ThemeExtension? other, double t) { + if (other is! AppThemeExtras) return this; + return t < 0.5 ? this : other; + } +} diff --git a/lib/features/ai_tutor/presentation/widgets/chat_input.dart b/lib/features/ai_tutor/presentation/widgets/chat_input.dart index f5095f9..8f14817 100644 --- a/lib/features/ai_tutor/presentation/widgets/chat_input.dart +++ b/lib/features/ai_tutor/presentation/widgets/chat_input.dart @@ -53,10 +53,12 @@ class _ChatInputState extends State { @override Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Container( padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.95), + color: cs.surface.withOpacity(0.98), borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), boxShadow: [ BoxShadow( @@ -169,17 +171,19 @@ class _ChatInputState extends State { } Widget _buildInputField(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), gradient: LinearGradient( colors: [ - Colors.grey[100]!, - Colors.grey[50]!, + cs.surfaceContainerHighest, + cs.surface, ], ), border: Border.all( - color: Colors.grey[300]!, + color: cs.outline.withOpacity(0.3), ), ), child: Row( @@ -190,9 +194,9 @@ class _ChatInputState extends State { controller: widget.controller, focusNode: _focusNode, maxLines: _isExpanded ? 5 : 1, - style: const TextStyle( + style: TextStyle( fontSize: 16, - color: Color(0xFF2D3748), + color: cs.onSurface, ), decoration: InputDecoration( hintText: 'Faça sua pergunta sobre o conteúdo...', @@ -253,8 +257,11 @@ class _ChatInputState extends State { height: 48, decoration: BoxDecoration( gradient: widget.controller.text.isNotEmpty - ? const LinearGradient( - colors: [Color(0xFF82C9BD), Color(0xFF6BA5A0)], + ? LinearGradient( + colors: [ + cs.primary, + cs.primary.withOpacity(0.85), + ], begin: Alignment.topLeft, end: Alignment.bottomRight, ) diff --git a/lib/features/ai_tutor/presentation/widgets/message_bubble.dart b/lib/features/ai_tutor/presentation/widgets/message_bubble.dart index bfe3f0d..fb63665 100644 --- a/lib/features/ai_tutor/presentation/widgets/message_bubble.dart +++ b/lib/features/ai_tutor/presentation/widgets/message_bubble.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import '../../../../core/services/rag_service.dart'; +import '../../../../core/theme/app_theme_extension.dart'; /// Widget for displaying chat messages with source citations class MessageBubble extends StatelessWidget { @@ -73,22 +74,28 @@ class MessageBubble extends StatelessWidget { } Widget _buildAvatar(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final extras = AppThemeExtras.of(context); + final accent = isUser ? cs.primary : cs.secondary; + return Container( width: 36, height: 36, decoration: BoxDecoration( gradient: isUser - ? const LinearGradient( - colors: [Color(0xFF82C9BD), Color(0xFF6BA5A0)], + ? LinearGradient( + colors: [ + extras.actionCardGradientStart, + extras.actionCardGradientEnd, + ], ) - : const LinearGradient( - colors: [Color(0xFFF68D2D), Color(0xFFE67E22)], + : LinearGradient( + colors: [cs.secondary, cs.secondary.withOpacity(0.85)], ), borderRadius: BorderRadius.circular(18), boxShadow: [ BoxShadow( - color: (isUser ? const Color(0xFF82C9BD) : const Color(0xFFF68D2D)) - .withOpacity(0.3), + color: accent.withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 2), ), @@ -103,6 +110,9 @@ class MessageBubble extends StatelessWidget { } Widget _buildMessageBubble(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final extras = AppThemeExtras.of(context); + return Container( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.75, @@ -110,15 +120,18 @@ class MessageBubble extends StatelessWidget { padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( gradient: isUser - ? const LinearGradient( - colors: [Color(0xFF82C9BD), Color(0xFF6BA5A0)], + ? LinearGradient( + colors: [ + extras.actionCardGradientStart, + extras.actionCardGradientEnd, + ], begin: Alignment.topLeft, end: Alignment.bottomRight, ) : LinearGradient( colors: [ - Colors.white.withOpacity(0.95), - Colors.white.withOpacity(0.9), + cs.surfaceContainerHighest, + cs.surface, ], begin: Alignment.topLeft, end: Alignment.bottomRight, @@ -154,24 +167,24 @@ class MessageBubble extends StatelessWidget { data: content, styleSheet: MarkdownStyleSheet( p: TextStyle( - color: const Color(0xFF2D3748), + color: cs.onSurface, fontSize: 16, height: 1.4, ), strong: TextStyle( - color: const Color(0xFF2D3748), + color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.bold, height: 1.4, ), em: TextStyle( - color: const Color(0xFF2D3748), + color: cs.onSurface, fontSize: 16, fontStyle: FontStyle.italic, height: 1.4, ), listBullet: TextStyle( - color: const Color(0xFF2D3748), + color: cs.onSurface, fontSize: 16, height: 1.4, ), @@ -262,7 +275,7 @@ class MessageBubble extends StatelessWidget { Icon( Icons.menu_book, size: 14, - color: const Color(0xFF82C9BD), + color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 6), Expanded( @@ -280,7 +293,7 @@ class MessageBubble extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: const Color(0xFF82C9BD).withOpacity(0.1), + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Text( @@ -288,7 +301,7 @@ class MessageBubble extends StatelessWidget { style: TextStyle( fontSize: 10, fontWeight: FontWeight.w600, - color: const Color(0xFF82C9BD), + color: Theme.of(context).colorScheme.primary, ), ), ), diff --git a/lib/features/auth/presentation/pages/login_page.dart b/lib/features/auth/presentation/pages/login_page.dart index 943218a..57fd31d 100644 --- a/lib/features/auth/presentation/pages/login_page.dart +++ b/lib/features/auth/presentation/pages/login_page.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../core/services/auth_service.dart'; import '../../../../core/services/session_service.dart'; +import '../../../../core/theme/app_theme_extension.dart'; import '../../../../shared/presentation/widgets/custom_notification.dart'; class LoginPage extends StatefulWidget { @@ -212,12 +213,16 @@ class _LoginPageState extends State { gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ - Theme.of(context).colorScheme.background, - Theme.of(context).colorScheme.primary.withOpacity(0.1), - Theme.of(context).colorScheme.secondary.withOpacity(0.05), - Theme.of(context).colorScheme.background, - ], + colors: Theme.of(context).brightness == Brightness.dark + ? AppThemeExtras.of(context).authBackgroundGradient + : [ + Theme.of(context).colorScheme.background, + Theme.of(context).colorScheme.primary + .withOpacity(0.1), + Theme.of(context).colorScheme.secondary + .withOpacity(0.05), + Theme.of(context).colorScheme.background, + ], ), ), child: SafeArea( diff --git a/lib/features/auth/presentation/pages/role_selection_page.dart b/lib/features/auth/presentation/pages/role_selection_page.dart index ec0bb43..6396446 100644 --- a/lib/features/auth/presentation/pages/role_selection_page.dart +++ b/lib/features/auth/presentation/pages/role_selection_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:go_router/go_router.dart'; -import '../../../../core/theme/app_colors.dart'; +import '../../../../core/theme/app_theme_extension.dart'; import '../../../../l10n/app_localizations.dart'; class RoleSelectionPage extends StatefulWidget { @@ -22,12 +22,14 @@ class _RoleSelectionPageState extends State { gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ - Theme.of(context).colorScheme.background, - Theme.of(context).colorScheme.primary.withOpacity(0.1), - Theme.of(context).colorScheme.secondary.withOpacity(0.05), - Theme.of(context).colorScheme.background, - ], + colors: Theme.of(context).brightness == Brightness.dark + ? AppThemeExtras.of(context).authBackgroundGradient + : [ + Theme.of(context).colorScheme.background, + Theme.of(context).colorScheme.primary.withOpacity(0.1), + Theme.of(context).colorScheme.secondary.withOpacity(0.05), + Theme.of(context).colorScheme.background, + ], ), ), child: Stack( @@ -51,10 +53,12 @@ class _RoleSelectionPageState extends State { width: 100, height: 100, decoration: BoxDecoration( - gradient: const LinearGradient( + gradient: LinearGradient( colors: [ - AppColors.gradientStart, - AppColors.gradientEnd, + AppThemeExtras.of(context) + .actionCardGradientStart, + AppThemeExtras.of(context) + .actionCardGradientEnd, ], begin: Alignment.topLeft, end: Alignment.bottomRight, @@ -62,9 +66,10 @@ class _RoleSelectionPageState extends State { borderRadius: BorderRadius.circular(25), boxShadow: [ BoxShadow( - color: AppColors.primaryBlue.withOpacity( - 0.3, - ), + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.3), blurRadius: 25, offset: const Offset(0, 10), ), @@ -113,15 +118,14 @@ class _RoleSelectionPageState extends State { const SizedBox(height: 12), ShaderMask( - shaderCallback: (bounds) => - const LinearGradient( - colors: [ - AppColors.primaryTeal, - AppColors.primaryOrange, - ], - begin: Alignment.centerLeft, - end: Alignment.centerRight, - ).createShader(bounds), + shaderCallback: (bounds) => LinearGradient( + colors: [ + Theme.of(context).colorScheme.primary, + Theme.of(context).colorScheme.secondary, + ], + begin: Alignment.centerLeft, + end: Alignment.centerRight, + ).createShader(bounds), child: Text( AppLocalizations.of(context)!.schoolName, style: Theme.of(context).textTheme.bodyMedium @@ -172,7 +176,9 @@ class _RoleSelectionPageState extends State { Text( 'Selecione o seu papel para continuar', style: Theme.of(context).textTheme.bodyLarge - ?.copyWith(color: AppColors.primaryOrange), + ?.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), ) .animate() .fadeIn( @@ -196,7 +202,7 @@ class _RoleSelectionPageState extends State { 'Aluno', Icons.school_outlined, 'student', - AppColors.gradientStart, + Theme.of(context).colorScheme.primary, ), ), const SizedBox(width: 16), @@ -206,7 +212,7 @@ class _RoleSelectionPageState extends State { 'Professor', Icons.person_outline, 'teacher', - AppColors.gradientEnd, + Theme.of(context).colorScheme.secondary, ), ), ], @@ -233,12 +239,15 @@ class _RoleSelectionPageState extends State { ? _handleContinue : null, style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primaryBlue, - foregroundColor: Colors.white, + backgroundColor: + Theme.of(context).colorScheme.primary, + foregroundColor: + Theme.of(context).colorScheme.onPrimary, elevation: 4, - shadowColor: AppColors.primaryBlue.withOpacity( - 0.3, - ), + shadowColor: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.3), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), @@ -322,12 +331,14 @@ class _RoleSelectionPageState extends State { end: Alignment.bottomRight, ) : null, - color: isSelected ? null : Colors.white, + color: isSelected + ? null + : Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(20), border: Border.all( color: isSelected ? gradientColor - : AppColors.primaryBlue.withOpacity(0.2), + : Theme.of(context).colorScheme.primary.withOpacity(0.2), width: isSelected ? 2 : 1, ), boxShadow: [ @@ -346,7 +357,9 @@ class _RoleSelectionPageState extends State { Icon( icon, size: 48, - color: isSelected ? Colors.white : AppColors.primaryBlue, + color: isSelected + ? Colors.white + : Theme.of(context).colorScheme.primary, ), const SizedBox(height: 12), Text( @@ -386,7 +399,7 @@ class _RoleSelectionPageState extends State { width: 4, height: 4, decoration: BoxDecoration( - color: AppColors.gradientStart.withOpacity(0.3), + color: Theme.of(context).colorScheme.primary.withOpacity(0.3), shape: BoxShape.circle, ), ) diff --git a/lib/features/auth/presentation/pages/signup_page.dart b/lib/features/auth/presentation/pages/signup_page.dart index bce9759..7ad2405 100644 --- a/lib/features/auth/presentation/pages/signup_page.dart +++ b/lib/features/auth/presentation/pages/signup_page.dart @@ -4,6 +4,7 @@ import 'package:flutter_animate/flutter_animate.dart'; import 'package:go_router/go_router.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../core/services/auth_service.dart'; +import '../../../../core/theme/app_theme_extension.dart'; import '../../../../shared/presentation/widgets/custom_notification.dart'; class SignupPage extends StatefulWidget { @@ -241,12 +242,16 @@ class _SignupPageState extends State { gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ - Theme.of(context).colorScheme.background, - Theme.of(context).colorScheme.primary.withOpacity(0.1), - Theme.of(context).colorScheme.secondary.withOpacity(0.05), - Theme.of(context).colorScheme.background, - ], + colors: Theme.of(context).brightness == Brightness.dark + ? AppThemeExtras.of(context).authBackgroundGradient + : [ + Theme.of(context).colorScheme.background, + Theme.of(context).colorScheme.primary + .withOpacity(0.1), + Theme.of(context).colorScheme.secondary + .withOpacity(0.05), + Theme.of(context).colorScheme.background, + ], ), ), child: SafeArea( @@ -364,7 +369,7 @@ class _SignupPageState extends State { ).colorScheme.onSurface, ), decoration: InputDecoration( - labelText: 'Nome Completo', + labelText: 'Primeiro Nome', labelStyle: TextStyle( color: Theme.of( context, diff --git a/lib/features/dashboard/presentation/pages/student_dashboard_page.dart b/lib/features/dashboard/presentation/pages/student_dashboard_page.dart index da45cc4..899b46e 100644 --- a/lib/features/dashboard/presentation/pages/student_dashboard_page.dart +++ b/lib/features/dashboard/presentation/pages/student_dashboard_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/services/auth_service.dart'; +import '../../../../core/theme/app_theme_extension.dart'; import '../widgets/progress_hero_widget.dart'; import '../widgets/quick_access_widget.dart'; import '../widgets/student_classes_list_widget.dart'; @@ -83,19 +84,17 @@ class _StudentDashboardPageState extends State { @override Widget build(BuildContext context) { + final themeExtras = AppThemeExtras.of(context); + final headerColor = themeExtras.dashboardHeaderTextColor; + return Scaffold( body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Theme.of(context).colorScheme.primary, - Theme.of(context).colorScheme.primary.withOpacity(0.8), - Theme.of(context).colorScheme.secondary, - Theme.of(context).colorScheme.background, - ], - stops: [0.0, 0.2, 0.6, 1.0], + colors: themeExtras.dashboardBackgroundGradient, + stops: themeExtras.dashboardGradientStops, ), ), child: SafeArea( @@ -114,17 +113,17 @@ class _StudentDashboardPageState extends State { children: [ Text( 'Bem-vindo, $_userName!', - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: headerColor, fontSize: 28, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), - const Text( + Text( 'Seu progresso de estudos', style: TextStyle( - color: Colors.white, + color: headerColor, fontSize: 16, fontWeight: FontWeight.w300, ), @@ -133,7 +132,7 @@ class _StudentDashboardPageState extends State { ), ), IconButton( - icon: const Icon(Icons.logout, color: Colors.white), + icon: Icon(Icons.logout, color: headerColor), onPressed: () async { await AuthService.signOut(); if (mounted) { diff --git a/lib/features/dashboard/presentation/pages/teacher_dashboard_page.dart b/lib/features/dashboard/presentation/pages/teacher_dashboard_page.dart index 8de0eda..1dd86d7 100644 --- a/lib/features/dashboard/presentation/pages/teacher_dashboard_page.dart +++ b/lib/features/dashboard/presentation/pages/teacher_dashboard_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/services/auth_service.dart'; +import '../../../../core/theme/app_theme_extension.dart'; import '../widgets/teacher_hero_widget.dart'; import '../widgets/teacher_quick_actions_widget.dart'; import '../widgets/teacher_classes_list_widget.dart'; @@ -79,19 +80,17 @@ class _TeacherDashboardPageState extends State { @override Widget build(BuildContext context) { + final themeExtras = AppThemeExtras.of(context); + final headerColor = themeExtras.dashboardHeaderTextColor; + return Scaffold( body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Theme.of(context).colorScheme.primary, - Theme.of(context).colorScheme.primary.withOpacity(0.8), - Theme.of(context).colorScheme.secondary, - Theme.of(context).colorScheme.background, - ], - stops: [0.0, 0.2, 0.6, 1.0], + colors: themeExtras.dashboardBackgroundGradient, + stops: themeExtras.dashboardGradientStops, ), ), child: SafeArea( @@ -110,17 +109,17 @@ class _TeacherDashboardPageState extends State { children: [ Text( 'Bem-vindo, $_userName!', - style: const TextStyle( - color: Colors.white, + style: TextStyle( + color: headerColor, fontSize: 28, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), - const Text( + Text( 'Painel de Gestão de Conteúdo', style: TextStyle( - color: Colors.white, + color: headerColor, fontSize: 16, fontWeight: FontWeight.w300, ), @@ -129,7 +128,7 @@ class _TeacherDashboardPageState extends State { ), ), IconButton( - icon: const Icon(Icons.logout, color: Colors.white), + icon: Icon(Icons.logout, color: headerColor), onPressed: () async { await AuthService.signOut(); if (mounted) { diff --git a/lib/features/dashboard/presentation/widgets/dashboard_action_card.dart b/lib/features/dashboard/presentation/widgets/dashboard_action_card.dart new file mode 100644 index 0000000..aa4bea0 --- /dev/null +++ b/lib/features/dashboard/presentation/widgets/dashboard_action_card.dart @@ -0,0 +1,308 @@ +import 'package:flutter/material.dart'; + +import '../../../../core/theme/app_theme_extension.dart'; + +/// Layout variant for dashboard quick-action cards. +enum DashboardActionCardLayout { vertical, horizontal } + +/// Reusable action card with flexible height and wrapping subtitles. +class DashboardActionCard extends StatelessWidget { + const DashboardActionCard({ + super.key, + required this.title, + required this.subtitle, + required this.icon, + required this.onTap, + this.layout = DashboardActionCardLayout.vertical, + this.minHeight = 150, + this.useGradient = false, + this.badge, + this.iconSize = 24, + this.iconPadding = 10, + this.titleFontSize, + this.subtitleFontSize, + this.padding, + this.leadingIcon, + this.onTapDisabled, + }); + + final String title; + final String subtitle; + final IconData icon; + final VoidCallback? onTap; + final bool? onTapDisabled; + final DashboardActionCardLayout layout; + final double minHeight; + final bool useGradient; + final String? badge; + final double iconSize; + final double iconPadding; + final double? titleFontSize; + final double? subtitleFontSize; + final EdgeInsetsGeometry? padding; + final Widget? leadingIcon; + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final extras = AppThemeExtras.of(context); + final isHorizontal = layout == DashboardActionCardLayout.horizontal; + final effectivePadding = + padding ?? EdgeInsets.all(isHorizontal ? 16 : (useGradient ? 20 : 14)); + + final decoration = BoxDecoration( + gradient: useGradient + ? LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + extras.actionCardGradientStart, + extras.actionCardGradientEnd, + ], + ) + : null, + color: useGradient ? null : cs.surface, + borderRadius: BorderRadius.circular(minHeight <= 110 ? 12 : 16), + border: useGradient + ? null + : Border.all(color: cs.outline.withOpacity(0.2), width: 1), + boxShadow: [ + BoxShadow( + color: (useGradient ? cs.primary : cs.shadow).withOpacity( + useGradient ? 0.3 : 0.05, + ), + blurRadius: useGradient ? 15 : 10, + offset: Offset(0, useGradient ? 8 : 4), + ), + ], + ); + + final titleColor = useGradient ? Colors.white : cs.onSurface; + final subtitleColor = + useGradient ? Colors.white : cs.onSurfaceVariant; + final iconBgColor = useGradient + ? Colors.white.withOpacity(0.2) + : cs.primary.withOpacity(0.1); + final iconColor = useGradient ? Colors.white : cs.primary; + + final effectiveMinHeight = minHeight > 0 ? minHeight : null; + + return ConstrainedBox( + constraints: effectiveMinHeight != null + ? BoxConstraints(minHeight: effectiveMinHeight) + : const BoxConstraints(), + child: Container( + decoration: decoration, + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(minHeight <= 110 ? 12 : 16), + onTap: onTapDisabled == true ? null : onTap, + child: Padding( + padding: effectivePadding, + child: isHorizontal + ? _buildHorizontalContent( + context, + titleColor, + subtitleColor, + iconBgColor, + iconColor, + ) + : _buildVerticalContent( + context, + titleColor, + subtitleColor, + iconBgColor, + iconColor, + ), + ), + ), + ), + ), + ); + } + + Widget _buildHorizontalContent( + BuildContext context, + Color titleColor, + Color subtitleColor, + Color iconBgColor, + Color iconColor, + ) { + final cs = Theme.of(context).colorScheme; + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _iconBox(iconBgColor, iconColor), + const SizedBox(width: 16), + Expanded( + child: _titleSubtitleColumn( + titleColor, + subtitleColor, + titleSize: titleFontSize ?? 16, + subtitleSize: subtitleFontSize ?? 13, + ), + ), + Icon(Icons.arrow_forward_ios, color: cs.primary, size: 16), + ], + ); + } + + Widget _buildVerticalContent( + BuildContext context, + Color titleColor, + Color subtitleColor, + Color iconBgColor, + Color iconColor, + ) { + final cs = Theme.of(context).colorScheme; + final isCompact = minHeight <= 130; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + leadingIcon ?? _iconBox(iconBgColor, iconColor), + if (badge != null) ...[ + const Spacer(), + Container( + padding: EdgeInsets.symmetric( + horizontal: isCompact ? 6 : 10, + vertical: isCompact ? 3 : 4, + ), + decoration: BoxDecoration( + color: cs.secondary, + borderRadius: BorderRadius.circular(isCompact ? 10 : 12), + ), + child: Text( + badge!, + style: TextStyle( + color: Colors.white, + fontSize: isCompact ? 9 : 10, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ], + ), + SizedBox(height: isCompact ? 8 : 12), + _titleSubtitleColumn( + titleColor, + subtitleColor, + titleSize: titleFontSize ?? (useGradient ? 18 : 16), + subtitleSize: subtitleFontSize ?? (isCompact ? 11 : 12), + ), + ], + ); + } + + Widget _iconBox(Color bgColor, Color iconColor) { + return Container( + padding: EdgeInsets.all(iconPadding), + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(10), + ), + child: Icon(icon, color: iconColor, size: iconSize), + ); + } + + Widget _titleSubtitleColumn( + Color titleColor, + Color subtitleColor, { + required double titleSize, + required double subtitleSize, + }) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + style: TextStyle( + color: titleColor, + fontSize: titleSize, + fontWeight: FontWeight.bold, + height: 1.2, + ), + ), + if (subtitle.isNotEmpty) ...[ + SizedBox(height: subtitleSize >= 13 ? 4 : 2), + Text( + subtitle, + style: TextStyle( + color: subtitleColor, + fontSize: subtitleSize, + height: 1.25, + ), + ), + ], + ], + ); + } +} + +/// Surface-styled vertical card (Quiz, Criar Turma, etc.). +class DashboardActionCardSurface extends StatelessWidget { + const DashboardActionCardSurface({ + super.key, + required this.title, + required this.subtitle, + required this.icon, + required this.onTap, + this.minHeight = 150, + this.iconColor, + this.leadingWidget, + this.onTapDisabled, + this.titleFontSize = 14, + this.subtitleFontSize = 11, + this.iconSize = 20, + this.padding = const EdgeInsets.all(12), + }); + + final String title; + final String subtitle; + final IconData icon; + final VoidCallback? onTap; + final bool? onTapDisabled; + final double minHeight; + final Color? iconColor; + final Widget? leadingWidget; + final double titleFontSize; + final double subtitleFontSize; + final double iconSize; + final EdgeInsetsGeometry padding; + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final effectiveIconColor = iconColor ?? cs.secondary; + + return DashboardActionCard( + title: title, + subtitle: subtitle, + icon: icon, + onTap: onTap, + onTapDisabled: onTapDisabled, + minHeight: minHeight, + useGradient: false, + iconSize: iconSize, + leadingIcon: leadingWidget ?? + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: effectiveIconColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Icon(icon, color: effectiveIconColor, size: iconSize), + ), + titleFontSize: titleFontSize, + subtitleFontSize: subtitleFontSize, + padding: padding, + ); + } +} diff --git a/lib/features/dashboard/presentation/widgets/profile_section_widget.dart b/lib/features/dashboard/presentation/widgets/profile_section_widget.dart index 359203c..f843891 100644 --- a/lib/features/dashboard/presentation/widgets/profile_section_widget.dart +++ b/lib/features/dashboard/presentation/widgets/profile_section_widget.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; + +import '../../../../core/theme/app_theme_extension.dart'; import 'package:flutter_animate/flutter_animate.dart'; import '../../../../core/services/auth_service.dart'; import '../../../../core/routing/app_router.dart'; @@ -42,10 +44,8 @@ class ProfileSectionWidget extends StatelessWidget { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - Theme.of(context).colorScheme.primary, - Theme.of( - context, - ).colorScheme.primary.withOpacity(0.8), + AppThemeExtras.of(context).actionCardGradientStart, + AppThemeExtras.of(context).actionCardGradientEnd, ], ), borderRadius: BorderRadius.circular(24), diff --git a/lib/features/dashboard/presentation/widgets/progress_hero_widget.dart b/lib/features/dashboard/presentation/widgets/progress_hero_widget.dart index 4eca529..bf158c8 100644 --- a/lib/features/dashboard/presentation/widgets/progress_hero_widget.dart +++ b/lib/features/dashboard/presentation/widgets/progress_hero_widget.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; +import '../../../../core/theme/app_theme_extension.dart'; + /// Progress tracking hero section for student dashboard class ProgressHeroWidget extends StatelessWidget { final String userName; @@ -148,8 +150,11 @@ class ProgressHeroWidget extends StatelessWidget { widthFactor: overallProgress, child: Container( decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [Colors.white, Color(0xFFF8F9FA)], + gradient: LinearGradient( + colors: [ + AppThemeExtras.of(context).heroProgressStart, + AppThemeExtras.of(context).heroProgressEnd, + ], ), borderRadius: BorderRadius.circular(6), ), diff --git a/lib/features/dashboard/presentation/widgets/quick_access_widget.dart b/lib/features/dashboard/presentation/widgets/quick_access_widget.dart index 8f5c33f..0da0dc0 100644 --- a/lib/features/dashboard/presentation/widgets/quick_access_widget.dart +++ b/lib/features/dashboard/presentation/widgets/quick_access_widget.dart @@ -2,8 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:go_router/go_router.dart'; import '../../../classes/presentation/pages/join_class_page.dart'; +import 'dashboard_action_card.dart'; -/// Quick access cards for Tutor IA and Quiz with fixed overflow +/// Quick access cards for Tutor IA and Quiz class QuickAccessWidget extends StatelessWidget { const QuickAccessWidget({super.key}); @@ -21,18 +22,23 @@ class QuickAccessWidget extends StatelessWidget { ), ), const SizedBox(height: 16), - - Row( - children: [ - // Tutor IA Card (Primary) - Expanded(flex: 3, child: _buildTutorIACard(context)), - const SizedBox(width: 16), - // Quiz Card (Secondary) - Expanded(flex: 2, child: _buildQuizCard(context)), - ], + IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + flex: 3, + child: _buildTutorIACard(context), + ), + const SizedBox(width: 16), + Expanded( + flex: 2, + child: _buildQuizCard(context), + ), + ], + ), ), const SizedBox(height: 16), - // Join Class Card _buildJoinClassCard(context), ], ) @@ -45,110 +51,13 @@ class QuickAccessWidget extends StatelessWidget { } Widget _buildTutorIACard(BuildContext context) { - return Container( - height: 150, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Theme.of(context).colorScheme.primary, - Theme.of(context).colorScheme.primary.withOpacity(0.8), - ], - ), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Theme.of(context).colorScheme.primary.withOpacity(0.3), - blurRadius: 15, - offset: const Offset(0, 8), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () { - print('DEBUG: AI Tutor card clicked!'); - try { - context.go('/ai-tutor'); - print('DEBUG: Navigation to AI Tutor successful'); - } catch (e) { - print('DEBUG: Navigation error: $e'); - } - }, - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(7), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon( - Icons.psychology, - color: Colors.white, - size: 24, - ), - ), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 4, - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondary, - borderRadius: BorderRadius.circular(12), - ), - child: const Text( - 'NOVO', - style: TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - const SizedBox(height: 12), - const Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Tutor IA', - style: TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - //SizedBox(height: 4), - Text( - 'Assistente de estudos', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Colors.white, - fontSize: 12, - height: 1.2, - ), - ), - ], - ), - ], - ), - ), - ), - ), + return DashboardActionCard( + title: 'Tutor IA', + subtitle: 'Assistente de estudos', + icon: Icons.psychology, + useGradient: true, + minHeight: 150, + onTap: () => context.go('/ai-tutor'), ) .animate() .scale( @@ -159,80 +68,14 @@ class QuickAccessWidget extends StatelessWidget { } Widget _buildQuizCard(BuildContext context) { - return Container( - height: 150, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of(context).colorScheme.outline.withOpacity(0.2), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Theme.of(context).colorScheme.shadow.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () => context.go('/quiz'), - child: Padding( - padding: const EdgeInsets.all(14), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.secondary.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.quiz, - color: Theme.of(context).colorScheme.secondary, - size: 24, - ), - ), - const SizedBox(height: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Quiz', - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text( - 'Testa os teus conhecimentos', - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant, - fontSize: 12, - height: 1.2, - ), - ), - ], - ), - ], - ), - ), - ), - ), + final cs = Theme.of(context).colorScheme; + return DashboardActionCardSurface( + title: 'Quiz', + subtitle: 'Testa os teus conhecimentos', + icon: Icons.quiz, + minHeight: 150, + iconColor: cs.secondary, + onTap: () => context.go('/quiz'), ) .animate() .scale( @@ -243,90 +86,18 @@ class QuickAccessWidget extends StatelessWidget { } Widget _buildJoinClassCard(BuildContext context) { - return Container( - height: 86, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of(context).colorScheme.outline.withOpacity(0.2), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Theme.of(context).colorScheme.shadow.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (_) => const JoinClassPage()), - ); - }, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.group_add, - color: Theme.of(context).colorScheme.primary, - size: 24, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Entrar numa Turma', - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 4), - Text( - 'Junta-te a uma turma com o código', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant, - fontSize: 13, - ), - ), - ], - ), - ), - Icon( - Icons.arrow_forward_ios, - color: Theme.of(context).colorScheme.primary, - size: 16, - ), - ], - ), - ), - ), - ), + return DashboardActionCard( + title: 'Entrar numa Turma', + subtitle: 'Junta-te a uma turma com o código', + icon: Icons.group_add, + layout: DashboardActionCardLayout.horizontal, + minHeight: 0, + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const JoinClassPage()), + ); + }, ) .animate() .scale( diff --git a/lib/features/dashboard/presentation/widgets/student_classes_list_widget.dart b/lib/features/dashboard/presentation/widgets/student_classes_list_widget.dart index 699ec8b..d8f12f1 100644 --- a/lib/features/dashboard/presentation/widgets/student_classes_list_widget.dart +++ b/lib/features/dashboard/presentation/widgets/student_classes_list_widget.dart @@ -107,7 +107,7 @@ class StudentClassesListWidget extends StatelessWidget { if (!snapshot.hasData || !snapshot.data!.exists) { return Container( width: 200, - height: 150, + constraints: const BoxConstraints(minHeight: 150), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, @@ -135,7 +135,7 @@ class StudentClassesListWidget extends StatelessWidget { return Container( width: 200, - height: 150, + constraints: const BoxConstraints(minHeight: 150), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, @@ -172,7 +172,7 @@ class StudentClassesListWidget extends StatelessWidget { fontSize: 16, fontWeight: FontWeight.bold, ), - maxLines: 1, + maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), diff --git a/lib/features/dashboard/presentation/widgets/teacher_analytics_preview_widget.dart b/lib/features/dashboard/presentation/widgets/teacher_analytics_preview_widget.dart index 08f054d..9058038 100644 --- a/lib/features/dashboard/presentation/widgets/teacher_analytics_preview_widget.dart +++ b/lib/features/dashboard/presentation/widgets/teacher_analytics_preview_widget.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; + +import '../../../../core/theme/app_theme_extension.dart'; import 'package:flutter_animate/flutter_animate.dart'; import '../../../../core/services/auth_service.dart'; @@ -41,10 +43,8 @@ class TeacherAnalyticsPreviewWidget extends StatelessWidget { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - Theme.of(context).colorScheme.primary, - Theme.of( - context, - ).colorScheme.primary.withOpacity(0.8), + AppThemeExtras.of(context).actionCardGradientStart, + AppThemeExtras.of(context).actionCardGradientEnd, ], ), borderRadius: BorderRadius.circular(24), @@ -288,8 +288,7 @@ class TeacherAnalyticsPreviewWidget extends StatelessWidget { label, style: TextStyle(color: color.withOpacity(0.8), fontSize: 10), textAlign: TextAlign.center, - maxLines: 2, - overflow: TextOverflow.ellipsis, + maxLines: 3, ), ], ), diff --git a/lib/features/dashboard/presentation/widgets/teacher_classes_list_widget.dart b/lib/features/dashboard/presentation/widgets/teacher_classes_list_widget.dart index bd3b77c..ea9773d 100644 --- a/lib/features/dashboard/presentation/widgets/teacher_classes_list_widget.dart +++ b/lib/features/dashboard/presentation/widgets/teacher_classes_list_widget.dart @@ -112,7 +112,7 @@ class TeacherClassesListWidget extends StatelessWidget { }, child: Container( width: 200, - height: 150, + constraints: const BoxConstraints(minHeight: 150), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, @@ -149,7 +149,7 @@ class TeacherClassesListWidget extends StatelessWidget { fontSize: 16, fontWeight: FontWeight.bold, ), - maxLines: 1, + maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), diff --git a/lib/features/dashboard/presentation/widgets/teacher_hero_widget.dart b/lib/features/dashboard/presentation/widgets/teacher_hero_widget.dart index c858046..ffe0a1a 100644 --- a/lib/features/dashboard/presentation/widgets/teacher_hero_widget.dart +++ b/lib/features/dashboard/presentation/widgets/teacher_hero_widget.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; +import '../../../../core/theme/app_theme_extension.dart'; + /// Hero section for teacher dashboard showing class overview class TeacherHeroWidget extends StatelessWidget { final String userName; @@ -34,8 +36,6 @@ class TeacherHeroWidget extends StatelessWidget { children: [ Text( 'Visão Geral da Turma', - maxLines: 1, - overflow: TextOverflow.ellipsis, style: TextStyle( color: Theme.of(context).colorScheme.onSurface, fontSize: 20, @@ -45,8 +45,6 @@ class TeacherHeroWidget extends StatelessWidget { const SizedBox(height: 4), Text( 'Acompanhe o progresso dos seus alunos', - maxLines: 1, - overflow: TextOverflow.ellipsis, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 13, @@ -148,8 +146,11 @@ class TeacherHeroWidget extends StatelessWidget { widthFactor: classAverageProgress, child: Container( decoration: BoxDecoration( - gradient: const LinearGradient( - colors: [Colors.white, Color(0xFFF8F9FA)], + gradient: LinearGradient( + colors: [ + AppThemeExtras.of(context).heroProgressStart, + AppThemeExtras.of(context).heroProgressEnd, + ], ), borderRadius: BorderRadius.circular(6), ), diff --git a/lib/features/dashboard/presentation/widgets/teacher_quick_actions_widget.dart b/lib/features/dashboard/presentation/widgets/teacher_quick_actions_widget.dart index 03ebce6..4d61426 100644 --- a/lib/features/dashboard/presentation/widgets/teacher_quick_actions_widget.dart +++ b/lib/features/dashboard/presentation/widgets/teacher_quick_actions_widget.dart @@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart'; import '../../../../core/services/auth_service.dart'; import '../../../../features/materials/presentation/pages/teacher_materials_page.dart'; +import 'dashboard_action_card.dart'; /// Quick access cards for teacher actions class TeacherQuickActionsWidget extends StatefulWidget { @@ -20,8 +21,25 @@ class TeacherQuickActionsWidget extends StatefulWidget { class _TeacherQuickActionsWidgetState extends State { bool _isCreatingClass = false; + /// Mesmas dimensões dos cards em "As Minhas Turmas". + static const double _scrollCardWidth = 200; + static const double _scrollRowHeight = 150; + static const double _cardMinHeight = 150; + static const EdgeInsets _cardPadding = EdgeInsets.all(16); + static const double _titleFontSize = 16; + static const double _subtitleFontSize = 13; + static const double _iconSize = 24; + static const double _iconPadding = 10; + @override Widget build(BuildContext context) { + final cards = [ + _buildUploadContentCard(context), + _buildCreateClassCard(context), + _buildCreateQuizCard(context), + _buildViewAnalyticsCard(context), + ]; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -33,29 +51,20 @@ class _TeacherQuickActionsWidgetState extends State { fontWeight: FontWeight.bold, ), ), - const SizedBox(height: 16), - - // Primary Actions Row - Row( - children: [ - // Upload Content Card (Primary) - Expanded(flex: 2, child: _buildUploadContentCard(context)), - const SizedBox(width: 16), - // Create Class Card - Expanded(flex: 2, child: _buildCreateClassCard(context)), - const SizedBox(width: 16), - // Create Quiz Card (Secondary) - Expanded(flex: 2, child: _buildCreateQuizCard(context)), - ], - ), - const SizedBox(height: 16), - - // Secondary Actions Row - Row( - children: [ - // View Analytics Card - Expanded(child: _buildViewAnalyticsCard(context)), - ], + const SizedBox(height: 12), + SizedBox( + height: _scrollRowHeight, + child: ListView.separated( + scrollDirection: Axis.horizontal, + clipBehavior: Clip.none, + padding: const EdgeInsets.only(right: 16), + itemCount: cards.length, + separatorBuilder: (_, __) => const SizedBox(width: 12), + itemBuilder: (context, index) => SizedBox( + width: _scrollCardWidth, + child: cards[index], + ), + ), ), ], ) @@ -70,284 +79,77 @@ class _TeacherQuickActionsWidgetState extends State { Widget _buildUploadContentCard(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(16), - child: - Container( - height: 150, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - Theme.of(context).colorScheme.primary, - Theme.of(context).colorScheme.primary.withOpacity(0.8), - ], - ), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Theme.of( - context, - ).colorScheme.primary.withOpacity(0.3), - blurRadius: 15, - offset: const Offset(0, 8), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const TeacherMaterialsPage(), - ), - ), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(10), - ), - child: const Icon( - Icons.upload_file, - color: Colors.white, - size: 20, - ), - ), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 3, - ), - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.secondary, - borderRadius: BorderRadius.circular(10), - ), - child: const Text( - 'NOVO', - style: TextStyle( - color: Colors.white, - fontSize: 9, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: const [ - Text( - 'Upload Conteúdo', - style: TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(height: 2), - Text( - 'PDFs, textos, imagens', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Colors.white, - fontSize: 11, - ), - ), - ], - ), - ], - ), - ), - ), - ), - ) - .animate() - .scale( - duration: const Duration(milliseconds: 600), - curve: Curves.elasticOut, - ) - .then(delay: const Duration(milliseconds: 100)), + child: DashboardActionCard( + title: 'Upload Conteúdo', + subtitle: 'PDFs, textos, imagens', + icon: Icons.upload_file, + useGradient: true, + minHeight: _cardMinHeight, + iconSize: _iconSize, + iconPadding: _iconPadding, + titleFontSize: _titleFontSize, + subtitleFontSize: _subtitleFontSize, + padding: _cardPadding, + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => const TeacherMaterialsPage(), + ), + ), + ) + .animate() + .scale( + duration: const Duration(milliseconds: 600), + curve: Curves.elasticOut, + ) + .then(delay: const Duration(milliseconds: 100)), ); } Widget _buildCreateQuizCard(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(16), - child: - Container( - height: 150, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of( - context, - ).colorScheme.outline.withOpacity(0.2), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Theme.of( - context, - ).colorScheme.shadow.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () => context.go('/teacher/quiz/create'), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.secondary.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.quiz, - color: Theme.of(context).colorScheme.secondary, - size: 20, - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Criar Quiz', - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurface, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 2), - Text( - 'Avaliações interativas', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant, - fontSize: 11, - ), - ), - ], - ), - ], - ), - ), - ), - ), - ) - .animate() - .scale( - duration: const Duration(milliseconds: 600), - curve: Curves.elasticOut, - ) - .then(delay: const Duration(milliseconds: 200)), + child: DashboardActionCardSurface( + title: 'Criar Quiz', + subtitle: 'Avaliações interativas', + icon: Icons.quiz, + minHeight: _cardMinHeight, + titleFontSize: _titleFontSize, + subtitleFontSize: _subtitleFontSize, + iconSize: _iconSize, + padding: _cardPadding, + onTap: () => context.go('/teacher/quiz/create'), + ) + .animate() + .scale( + duration: const Duration(milliseconds: 600), + curve: Curves.elasticOut, + ) + .then(delay: const Duration(milliseconds: 200)), ); } Widget _buildViewAnalyticsCard(BuildContext context) { - return Container( - height: 120, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of(context).colorScheme.outline.withOpacity(0.2), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Theme.of(context).colorScheme.shadow.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () => context.go('/teacher/analytics'), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.primary.withOpacity(0.8), - borderRadius: BorderRadius.circular(10), - ), - child: Icon( - Icons.analytics, - color: Theme.of(context).colorScheme.primary, - size: 20, - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Analytics', - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - Text( - 'Desempenho da turma', - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant, - fontSize: 11, - ), - ), - ], - ), - ], - ), - ), + final cs = Theme.of(context).colorScheme; + return DashboardActionCardSurface( + title: 'Analytics', + subtitle: 'Desempenho da turma', + icon: Icons.analytics, + minHeight: _cardMinHeight, + titleFontSize: _titleFontSize, + subtitleFontSize: _subtitleFontSize, + iconSize: _iconSize, + padding: _cardPadding, + iconColor: cs.primary, + leadingWidget: Container( + padding: const EdgeInsets.all(_iconPadding), + decoration: BoxDecoration( + color: cs.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), ), + child: Icon(Icons.analytics, color: cs.primary, size: _iconSize), ), + onTap: () => context.go('/teacher/analytics'), ) .animate() .scale( @@ -358,110 +160,45 @@ class _TeacherQuickActionsWidgetState extends State { } Widget _buildCreateClassCard(BuildContext context) { + final cs = Theme.of(context).colorScheme; return ClipRRect( borderRadius: BorderRadius.circular(16), - child: - Container( - height: 150, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: Theme.of( - context, - ).colorScheme.outline.withOpacity(0.2), - width: 1, - ), - boxShadow: [ - BoxShadow( - color: Theme.of( - context, - ).colorScheme.shadow.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: _isCreatingClass - ? null - : () => _showCreateClassDialog(context), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.primary.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: _isCreatingClass - ? SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Theme.of( - context, - ).colorScheme.primary, - ), - ) - : Icon( - Icons.school, - color: Theme.of( - context, - ).colorScheme.primary, - size: 20, - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Criar Turma', - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurface, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 2), - Text( - 'Gerar código de acesso', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant, - fontSize: 11, - ), - ), - ], - ), - ], + child: DashboardActionCardSurface( + title: 'Criar Turma', + subtitle: 'Gerar código de acesso', + icon: Icons.school, + minHeight: _cardMinHeight, + titleFontSize: _titleFontSize, + subtitleFontSize: _subtitleFontSize, + iconSize: _iconSize, + padding: _cardPadding, + iconColor: cs.primary, + onTapDisabled: _isCreatingClass, + onTap: () => _showCreateClassDialog(context), + leadingWidget: Container( + padding: const EdgeInsets.all(_iconPadding), + decoration: BoxDecoration( + color: cs.primary.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: _isCreatingClass + ? SizedBox( + width: _iconSize, + height: _iconSize, + child: CircularProgressIndicator( + strokeWidth: 2, + color: cs.primary, ), - ), - ), - ), - ) - .animate() - .scale( - duration: const Duration(milliseconds: 600), - curve: Curves.elasticOut, - ) - .then(delay: const Duration(milliseconds: 150)), + ) + : Icon(Icons.school, color: cs.primary, size: _iconSize), + ), + ) + .animate() + .scale( + duration: const Duration(milliseconds: 600), + curve: Curves.elasticOut, + ) + .then(delay: const Duration(milliseconds: 150)), ); } diff --git a/lib/features/materials/presentation/pages/teacher_materials_page.dart b/lib/features/materials/presentation/pages/teacher_materials_page.dart index 63a9461..81ec316 100644 --- a/lib/features/materials/presentation/pages/teacher_materials_page.dart +++ b/lib/features/materials/presentation/pages/teacher_materials_page.dart @@ -121,8 +121,8 @@ class _TeacherMaterialsPageState extends State { Text( 'Erro ao carregar materiais:\n${snapshot.error}', textAlign: TextAlign.center, - style: const TextStyle( - color: Color(0xFF2D3748), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, fontSize: 16, ), ), @@ -134,28 +134,28 @@ class _TeacherMaterialsPageState extends State { final materials = snapshot.data?.docs ?? []; if (materials.isEmpty) { - return const Center( + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.folder_open, - color: Color(0xFF718096), + color: Theme.of(context).colorScheme.onSurfaceVariant, size: 64, ), - SizedBox(height: 16), + const SizedBox(height: 16), Text( 'Nenhum material enviado ainda.', style: TextStyle( - color: Color(0xFF718096), + color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 16, ), ), - SizedBox(height: 8), + const SizedBox(height: 8), Text( 'Os materiais enviados aparecerão aqui.', style: TextStyle( - color: Color(0xFF9CA3AF), + color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 14, ), ), @@ -226,12 +226,12 @@ class _TeacherMaterialsPageState extends State { ), ), const SizedBox(height: 20), - const Text( + Text( 'Adicionar Material', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, - color: Color(0xFF2D3748), + color: Theme.of(context).colorScheme.onSurface, ), ), const SizedBox(height: 20), @@ -318,10 +318,10 @@ class _TeacherMaterialsPageState extends State { children: [ Text( title, - style: const TextStyle( + style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Color(0xFF2D3748), + color: Theme.of(context).colorScheme.onSurface, ), ), const SizedBox(height: 2), @@ -786,12 +786,12 @@ class _TeacherMaterialsPageState extends State { ), title: Text( fileName, - style: const TextStyle( + style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, - color: Color(0xFF2D3748), + color: Theme.of(context).colorScheme.onSurface, ), - maxLines: 1, + maxLines: 2, overflow: TextOverflow.ellipsis, ), subtitle: classId != null @@ -805,8 +805,8 @@ class _TeacherMaterialsPageState extends State { children: [ Text( formattedDate, - style: const TextStyle( - color: Color(0xFF718096), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 13, ), ), @@ -814,21 +814,21 @@ class _TeacherMaterialsPageState extends State { const SizedBox(height: 2), Row( children: [ - const Icon( + Icon( Icons.school_outlined, size: 12, - color: Color(0xFF82C9BD), + color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 4), Flexible( child: Text( className, - style: const TextStyle( - color: Color(0xFF82C9BD), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, fontSize: 12, fontWeight: FontWeight.w500, ), - maxLines: 1, + maxLines: 2, overflow: TextOverflow.ellipsis, ), ), @@ -841,8 +841,8 @@ class _TeacherMaterialsPageState extends State { ) : Text( formattedDate, - style: const TextStyle( - color: Color(0xFF718096), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 13, ), ), diff --git a/lib/features/quiz/presentation/pages/quiz_list_page.dart b/lib/features/quiz/presentation/pages/quiz_list_page.dart index 88c411a..75e2126 100644 --- a/lib/features/quiz/presentation/pages/quiz_list_page.dart +++ b/lib/features/quiz/presentation/pages/quiz_list_page.dart @@ -646,7 +646,7 @@ class _InteractiveQuizSheetState extends State<_InteractiveQuizSheet> { children: [ Text( widget.title, - maxLines: 1, + maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: cs.onSurface), ), @@ -994,7 +994,7 @@ class _TeacherQuizInteractiveSheetState extends State<_TeacherQuizInteractiveShe crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(widget.title, - maxLines: 1, + maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: cs.onSurface)), if (!_submitted) diff --git a/lib/features/quiz/presentation/pages/teacher_quiz_page.dart b/lib/features/quiz/presentation/pages/teacher_quiz_page.dart index 96e04fa..2874cc4 100644 --- a/lib/features/quiz/presentation/pages/teacher_quiz_page.dart +++ b/lib/features/quiz/presentation/pages/teacher_quiz_page.dart @@ -504,6 +504,7 @@ class _QuizEditorPageState extends State<_QuizEditorPage> { appBar: AppBar( title: Text( widget.materialName.replaceAll('.pdf', '').replaceAll('_', ' '), + maxLines: 2, overflow: TextOverflow.ellipsis, ), backgroundColor: cs.surface, @@ -693,7 +694,7 @@ class _QuestionEditorState extends State<_QuestionEditor> { Expanded( child: Text( widget.question.question.isEmpty ? 'Pergunta ${widget.index + 1}' : widget.question.question, - maxLines: 1, + maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: cs.onSurface), ), diff --git a/lib/features/settings/presentation/pages/settings_page.dart b/lib/features/settings/presentation/pages/settings_page.dart index c7e3b2b..de7ce82 100644 --- a/lib/features/settings/presentation/pages/settings_page.dart +++ b/lib/features/settings/presentation/pages/settings_page.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/theme/app_colors.dart'; +import '../../../../core/theme/app_theme_extension.dart'; import '../../../../core/services/theme_service.dart'; import '../../../../core/providers/theme_provider.dart'; @@ -34,16 +35,12 @@ class _SettingsPageState extends ConsumerState { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: themeMode == ThemeMode.light - ? [ - const Color(0xFFD4E8E8), - const Color(0xFFE8D4C0), - const Color(0xFFD8E0E8), + ? const [ + Color(0xFFD4E8E8), + Color(0xFFE8D4C0), + Color(0xFFD8E0E8), ] - : [ - Theme.of(context).colorScheme.primary.withOpacity(0.1), - Theme.of(context).colorScheme.secondary.withOpacity(0.05), - Theme.of(context).colorScheme.background, - ], + : AppThemeExtras.of(context).authBackgroundGradient, ), ), child: SafeArea( @@ -55,7 +52,10 @@ class _SettingsPageState extends ConsumerState { child: Row( children: [ IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: Icon( + Icons.arrow_back, + color: Theme.of(context).colorScheme.onSurface, + ), onPressed: () => context.go('/student-dashboard'), ), Expanded( diff --git a/lib/features/splash/presentation/pages/splash_page.dart b/lib/features/splash/presentation/pages/splash_page.dart index 7274baf..eb5d716 100644 --- a/lib/features/splash/presentation/pages/splash_page.dart +++ b/lib/features/splash/presentation/pages/splash_page.dart @@ -3,6 +3,7 @@ import 'package:flutter_animate/flutter_animate.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/services/auth_service.dart'; import '../../../../core/services/session_service.dart'; +import '../../../../core/theme/app_theme_extension.dart'; import '../../../../l10n/app_localizations.dart'; class SplashPage extends StatefulWidget { @@ -117,12 +118,14 @@ class _SplashPageState extends State { gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, - colors: [ - Theme.of(context).colorScheme.background, - Theme.of(context).colorScheme.primary.withOpacity(0.1), - Theme.of(context).colorScheme.secondary.withOpacity(0.05), - Theme.of(context).colorScheme.background, - ], + colors: Theme.of(context).brightness == Brightness.dark + ? AppThemeExtras.of(context).authBackgroundGradient + : [ + Theme.of(context).colorScheme.background, + Theme.of(context).colorScheme.primary.withOpacity(0.1), + Theme.of(context).colorScheme.secondary.withOpacity(0.05), + Theme.of(context).colorScheme.background, + ], ), ), child: Stack(