Modificação nos modos, correção de textos cortados

This commit is contained in:
2026-05-17 14:02:42 +01:00
parent 14509c04d3
commit 51ea446ae9
24 changed files with 906 additions and 933 deletions

View File

@@ -1,111 +1,90 @@
import 'package:flutter/material.dart'; 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 { class AppColors {
// Primary Brand Colors (same for both modes) // Primary Brand Colors (light mode)
static const Color primaryTeal = Color( static const Color primaryTeal = Color(0xFF82C9BD);
0xFF82C9BD, static const Color primaryOrange = Color(0xFFF68D2D);
); // Main teal color - PRIMARY
static const Color primaryOrange = Color(
0xFFF68D2D,
); // Accent orange - SECONDARY
// Gradient Colors (same for both modes) // Gradient Colors (light mode)
static const Color gradientStart = Color(0xFF82C9BD); // Teal gradient start static const Color gradientStart = Color(0xFF82C9BD);
static const Color gradientEnd = Color( static const Color gradientEnd = Color(0xFF6AB8A8);
0xFF6AB8A8,
); // Darker teal gradient end
// Secondary Colors (same for both modes) // Secondary Colors (light mode)
static const Color secondaryTeal = Color(0xFF6AB8A8); // Darker teal static const Color secondaryTeal = Color(0xFF6AB8A8);
static const Color accentTeal = Color(0xFF5AA69A); // Lighter teal accent static const Color accentTeal = Color(0xFF5AA69A);
static const Color lightOrange = Color(0xFFF7A960); // Lighter orange static const Color lightOrange = Color(0xFFF7A960);
// Status Colors (same for both modes) // Status Colors (shared)
static const Color success = Color(0xFF10B981); // Green for success static const Color success = Color(0xFF10B981);
static const Color warning = Color(0xFFF59E0B); // Amber for warnings static const Color warning = Color(0xFFF59E0B);
static const Color error = Color(0xFFEF4444); // Red for errors static const Color error = Color(0xFFEF4444);
static const Color info = Color(0xFF3B82F6); // Blue for info static const Color info = Color(0xFF3B82F6);
// Legacy compatibility (for existing code) @Deprecated('Use AppColors.primaryTeal')
@deprecated static const Color primaryBlue = primaryTeal;
static const Color primaryBlue = primaryTeal; // Map old primaryBlue to new 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 /// Light Mode Colors
class LightColors { class LightColors {
// Neutral Colors static const Color background = Color(0xFFF8F9FA);
static const Color background = Color(0xFFF8F9FA); // Light gray background static const Color surface = Color(0xFFFFFFFF);
static const Color surface = Color(0xFFFFFFFF); // White surfaces static const Color surfaceVariant = Color(0xFFF3F4F6);
static const Color cardBackground = Color(0xFFFFFFFF); // White cards static const Color cardBackground = Color(0xFFFFFFFF);
// Text Colors static const Color textPrimary = Color(0xFF1A1A1A);
static const Color textPrimary = Color(0xFF1A1A1A); // Primary text static const Color textSecondary = Color(0xFF6B7280);
static const Color textSecondary = Color(0xFF6B7280); // Secondary text static const Color textHint = Color(0xFF9CA3AF);
static const Color textHint = Color(0xFF9CA3AF); // Hint text
// Interactive Colors static const Color buttonPrimary = AppColors.primaryTeal;
static const Color buttonPrimary = static const Color buttonAccent = AppColors.primaryOrange;
AppColors.primaryTeal; // Primary button (teal) static const Color buttonSecondary = Color(0xFFE5E7EB);
static const Color buttonAccent = static const Color iconActive = AppColors.primaryTeal;
AppColors.primaryOrange; // Accent button (orange) static const Color iconInactive = Color(0xFF9CA3AF);
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
// Chat Specific Colors static const Color chatBubbleStudent = AppColors.primaryTeal;
static const Color chatBubbleStudent = static const Color chatBubbleAI = Color(0xFFF3F4F6);
AppColors.primaryTeal; // Student messages (teal) static const Color chatInputBackground = Color(0xFFF8F9FA);
static const Color chatBubbleAI = Color(0xFFF3F4F6); // AI messages static const Color chatSendButton = AppColors.primaryTeal;
static const Color chatInputBackground = Color(
0xFFF8F9FA,
); // Input background
static const Color chatSendButton =
AppColors.primaryTeal; // Send button (teal)
// Border Colors static const Color border = Color(0xFFE2E8F0);
static const Color border = Color(0xFFE2E8F0); // Border color static const Color divider = Color(0xFFE5E7EB);
static const Color divider = Color(0xFFE5E7EB); // Divider color static const Color overlay = Color(0x80000000);
// Overlay Colors
static const Color overlay = Color(0x80000000); // Overlay color
} }
/// Dark Mode Colors /// Dark Mode Colors
class DarkColors { class DarkColors {
// Neutral Colors static const Color background = Color(0xFF0F1218);
static const Color background = Color(0xFF1F2937); // Dark background static const Color surface = Color(0xFF1A2332);
static const Color surface = Color(0xFF374151); // Dark surface static const Color surfaceVariant = Color(0xFF243044);
static const Color cardBackground = Color(0xFF374151); // Dark cards static const Color cardBackground = Color(0xFF1A2332);
// Text Colors static const Color textPrimary = Color(0xFFF9FAFB);
static const Color textPrimary = Color(0xFFF9FAFB); // Dark primary text static const Color textSecondary = Color(0xFFD1D5DB);
static const Color textSecondary = Color(0xFFD1D5DB); // Dark secondary text static const Color textHint = Color(0xFF9CA3AF);
static const Color textHint = Color(0xFF9CA3AF); // Hint text
// Interactive Colors static const Color buttonPrimary = DarkBrandColors.primaryTeal;
static const Color buttonPrimary = static const Color buttonAccent = DarkBrandColors.primaryOrange;
AppColors.primaryTeal; // Primary button (teal) static const Color buttonSecondary = Color(0xFF2D3A4D);
static const Color buttonAccent = static const Color iconActive = DarkBrandColors.primaryTeal;
AppColors.primaryOrange; // Accent button (orange) static const Color iconInactive = Color(0xFF6B7280);
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
// Chat Specific Colors static const Color chatBubbleStudent = DarkBrandColors.primaryTeal;
static const Color chatBubbleStudent = static const Color chatBubbleAI = Color(0xFF243044);
AppColors.primaryTeal; // Student messages (teal) static const Color chatInputBackground = Color(0xFF1A2332);
static const Color chatBubbleAI = Color(0xFF4B5563); // AI messages (darker) static const Color chatSendButton = DarkBrandColors.primaryTeal;
static const Color chatInputBackground = Color(
0xFF374151,
); // Input background
static const Color chatSendButton =
AppColors.primaryTeal; // Send button (teal)
// Border Colors static const Color border = Color(0xFF2D3A4D);
static const Color border = Color(0xFF4B5563); // Border color static const Color divider = Color(0xFF2D3A4D);
static const Color divider = Color(0xFF4B5563); // Divider color static const Color overlay = Color(0x80000000);
// Overlay Colors
static const Color overlay = Color(0x80000000); // Overlay color
} }

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'app_colors.dart'; import 'app_colors.dart';
import 'app_theme_extension.dart';
/// Application Theme Configuration /// Application Theme Configuration
class AppTheme { class AppTheme {
@@ -12,11 +13,18 @@ class AppTheme {
seedColor: AppColors.primaryTeal, seedColor: AppColors.primaryTeal,
brightness: Brightness.light, brightness: Brightness.light,
primary: AppColors.primaryTeal, primary: AppColors.primaryTeal,
onPrimary: Colors.white,
secondary: AppColors.primaryOrange, secondary: AppColors.primaryOrange,
onSecondary: Colors.white,
surface: LightColors.surface, surface: LightColors.surface,
onSurface: LightColors.textPrimary,
onSurfaceVariant: LightColors.textSecondary,
surfaceContainerHighest: LightColors.surfaceVariant,
background: LightColors.background, background: LightColors.background,
error: AppColors.error, error: AppColors.error,
), ),
scaffoldBackgroundColor: LightColors.background,
extensions: [AppThemeExtras.light],
// App Bar Theme // App Bar Theme
appBarTheme: const AppBarTheme( appBarTheme: const AppBarTheme(
@@ -256,17 +264,22 @@ class AppTheme {
return ThemeData( return ThemeData(
useMaterial3: true, useMaterial3: true,
brightness: Brightness.dark, brightness: Brightness.dark,
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.dark(
seedColor: AppColors.primaryTeal, primary: DarkBrandColors.primaryTeal,
brightness: Brightness.dark, onPrimary: Colors.white,
primary: AppColors.primaryTeal, secondary: DarkBrandColors.primaryOrange,
secondary: AppColors.primaryOrange, onSecondary: Colors.white,
surface: DarkColors.surface, surface: DarkColors.surface,
onSurface: DarkColors.textPrimary,
onSurfaceVariant: DarkColors.textSecondary,
surfaceContainerHighest: DarkColors.surfaceVariant,
surfaceContainerLow: DarkColors.background,
background: DarkColors.background, background: DarkColors.background,
error: AppColors.error, error: AppColors.error,
outline: DarkColors.border,
), ),
scaffoldBackgroundColor: DarkColors.background,
// Dark mode specific overrides would go here extensions: [AppThemeExtras.dark],
appBarTheme: const AppBarTheme( appBarTheme: const AppBarTheme(
backgroundColor: DarkColors.surface, backgroundColor: DarkColors.surface,
foregroundColor: DarkColors.textPrimary, foregroundColor: DarkColors.textPrimary,
@@ -294,7 +307,7 @@ class AppTheme {
backgroundColor: DarkColors.buttonPrimary, backgroundColor: DarkColors.buttonPrimary,
foregroundColor: Colors.white, foregroundColor: Colors.white,
elevation: 2, elevation: 2,
shadowColor: AppColors.primaryTeal.withOpacity(0.3), shadowColor: DarkBrandColors.primaryTeal.withOpacity(0.3),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@@ -306,8 +319,10 @@ class AppTheme {
// Outlined Button Theme for Dark Mode // Outlined Button Theme for Dark Mode
outlinedButtonTheme: OutlinedButtonThemeData( outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: AppColors.primaryTeal, foregroundColor: DarkBrandColors.primaryTeal,
side: BorderSide(color: AppColors.primaryTeal.withOpacity(0.3)), side: BorderSide(
color: DarkBrandColors.primaryTeal.withOpacity(0.3),
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@@ -319,7 +334,7 @@ class AppTheme {
// Text Button Theme for Dark Mode // Text Button Theme for Dark Mode
textButtonTheme: TextButtonThemeData( textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: AppColors.primaryTeal, foregroundColor: DarkBrandColors.primaryTeal,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
@@ -332,15 +347,22 @@ class AppTheme {
fillColor: DarkColors.surface, fillColor: DarkColors.surface,
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.primaryTeal.withOpacity(0.3)), borderSide: BorderSide(
color: DarkBrandColors.primaryTeal.withOpacity(0.3),
),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: AppColors.primaryTeal.withOpacity(0.3)), borderSide: BorderSide(
color: DarkBrandColors.primaryTeal.withOpacity(0.3),
),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: AppColors.primaryTeal, width: 2), borderSide: const BorderSide(
color: DarkBrandColors.primaryTeal,
width: 2,
),
), ),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -363,9 +385,9 @@ class AppTheme {
// Text Field Theme for Dark Mode // Text Field Theme for Dark Mode
textSelectionTheme: TextSelectionThemeData( textSelectionTheme: TextSelectionThemeData(
cursorColor: AppColors.primaryTeal, cursorColor: DarkBrandColors.primaryTeal,
selectionColor: AppColors.primaryTeal.withOpacity(0.3), selectionColor: DarkBrandColors.primaryTeal.withOpacity(0.3),
selectionHandleColor: AppColors.primaryTeal, selectionHandleColor: DarkBrandColors.primaryTeal,
), ),
textTheme: const TextTheme( textTheme: const TextTheme(
@@ -449,7 +471,7 @@ class AppTheme {
// Bottom Navigation Bar Theme for Dark Mode // Bottom Navigation Bar Theme for Dark Mode
bottomNavigationBarTheme: const BottomNavigationBarThemeData( bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: DarkColors.surface, backgroundColor: DarkColors.surface,
selectedItemColor: AppColors.primaryTeal, selectedItemColor: DarkBrandColors.primaryTeal,
unselectedItemColor: DarkColors.iconInactive, unselectedItemColor: DarkColors.iconInactive,
type: BottomNavigationBarType.fixed, type: BottomNavigationBarType.fixed,
elevation: 8, elevation: 8,
@@ -465,7 +487,7 @@ class AppTheme {
// Floating Action Button Theme for Dark Mode // Floating Action Button Theme for Dark Mode
floatingActionButtonTheme: FloatingActionButtonThemeData( floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: AppColors.primaryTeal, backgroundColor: DarkBrandColors.primaryTeal,
foregroundColor: Colors.white, foregroundColor: Colors.white,
elevation: 4, elevation: 4,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
@@ -483,7 +505,7 @@ class AppTheme {
// Progress Indicator Theme for Dark Mode // Progress Indicator Theme for Dark Mode
progressIndicatorTheme: const ProgressIndicatorThemeData( progressIndicatorTheme: const ProgressIndicatorThemeData(
color: AppColors.primaryTeal, color: DarkBrandColors.primaryTeal,
linearTrackColor: DarkColors.buttonSecondary, linearTrackColor: DarkColors.buttonSecondary,
circularTrackColor: DarkColors.buttonSecondary, circularTrackColor: DarkColors.buttonSecondary,
), ),
@@ -491,7 +513,7 @@ class AppTheme {
// Chip Theme for Dark Mode // Chip Theme for Dark Mode
chipTheme: ChipThemeData( chipTheme: ChipThemeData(
backgroundColor: DarkColors.buttonSecondary, backgroundColor: DarkColors.buttonSecondary,
selectedColor: AppColors.primaryTeal.withOpacity(0.1), selectedColor: DarkBrandColors.primaryTeal.withOpacity(0.1),
disabledColor: DarkColors.buttonSecondary.withOpacity(0.5), disabledColor: DarkColors.buttonSecondary.withOpacity(0.5),
labelStyle: const TextStyle(color: DarkColors.textPrimary), labelStyle: const TextStyle(color: DarkColors.textPrimary),
secondaryLabelStyle: const TextStyle(color: DarkColors.textPrimary), secondaryLabelStyle: const TextStyle(color: DarkColors.textPrimary),

View File

@@ -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<AppThemeExtras> {
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<Color> dashboardBackgroundGradient;
final List<double> dashboardGradientStops;
final Color heroProgressStart;
final Color heroProgressEnd;
final Color actionCardGradientStart;
final Color actionCardGradientEnd;
final List<Color> 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<AppThemeExtras>() ?? light;
}
@override
AppThemeExtras copyWith({
List<Color>? dashboardBackgroundGradient,
List<double>? dashboardGradientStops,
Color? heroProgressStart,
Color? heroProgressEnd,
Color? actionCardGradientStart,
Color? actionCardGradientEnd,
List<Color>? 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<AppThemeExtras>? other, double t) {
if (other is! AppThemeExtras) return this;
return t < 0.5 ? this : other;
}
}

View File

@@ -53,10 +53,12 @@ class _ChatInputState extends State<ChatInput> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Container( return Container(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95), color: cs.surface.withOpacity(0.98),
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@@ -169,17 +171,19 @@ class _ChatInputState extends State<ChatInput> {
} }
Widget _buildInputField(BuildContext context) { Widget _buildInputField(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
Colors.grey[100]!, cs.surfaceContainerHighest,
Colors.grey[50]!, cs.surface,
], ],
), ),
border: Border.all( border: Border.all(
color: Colors.grey[300]!, color: cs.outline.withOpacity(0.3),
), ),
), ),
child: Row( child: Row(
@@ -190,9 +194,9 @@ class _ChatInputState extends State<ChatInput> {
controller: widget.controller, controller: widget.controller,
focusNode: _focusNode, focusNode: _focusNode,
maxLines: _isExpanded ? 5 : 1, maxLines: _isExpanded ? 5 : 1,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: Color(0xFF2D3748), color: cs.onSurface,
), ),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Faça sua pergunta sobre o conteúdo...', hintText: 'Faça sua pergunta sobre o conteúdo...',
@@ -253,8 +257,11 @@ class _ChatInputState extends State<ChatInput> {
height: 48, height: 48,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: widget.controller.text.isNotEmpty gradient: widget.controller.text.isNotEmpty
? const LinearGradient( ? LinearGradient(
colors: [Color(0xFF82C9BD), Color(0xFF6BA5A0)], colors: [
cs.primary,
cs.primary.withOpacity(0.85),
],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
) )

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import '../../../../core/services/rag_service.dart'; import '../../../../core/services/rag_service.dart';
import '../../../../core/theme/app_theme_extension.dart';
/// Widget for displaying chat messages with source citations /// Widget for displaying chat messages with source citations
class MessageBubble extends StatelessWidget { class MessageBubble extends StatelessWidget {
@@ -73,22 +74,28 @@ class MessageBubble extends StatelessWidget {
} }
Widget _buildAvatar(BuildContext context) { Widget _buildAvatar(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final extras = AppThemeExtras.of(context);
final accent = isUser ? cs.primary : cs.secondary;
return Container( return Container(
width: 36, width: 36,
height: 36, height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: isUser gradient: isUser
? const LinearGradient( ? LinearGradient(
colors: [Color(0xFF82C9BD), Color(0xFF6BA5A0)], colors: [
extras.actionCardGradientStart,
extras.actionCardGradientEnd,
],
) )
: const LinearGradient( : LinearGradient(
colors: [Color(0xFFF68D2D), Color(0xFFE67E22)], colors: [cs.secondary, cs.secondary.withOpacity(0.85)],
), ),
borderRadius: BorderRadius.circular(18), borderRadius: BorderRadius.circular(18),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: (isUser ? const Color(0xFF82C9BD) : const Color(0xFFF68D2D)) color: accent.withOpacity(0.3),
.withOpacity(0.3),
blurRadius: 8, blurRadius: 8,
offset: const Offset(0, 2), offset: const Offset(0, 2),
), ),
@@ -103,6 +110,9 @@ class MessageBubble extends StatelessWidget {
} }
Widget _buildMessageBubble(BuildContext context) { Widget _buildMessageBubble(BuildContext context) {
final cs = Theme.of(context).colorScheme;
final extras = AppThemeExtras.of(context);
return Container( return Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.75, maxWidth: MediaQuery.of(context).size.width * 0.75,
@@ -110,15 +120,18 @@ class MessageBubble extends StatelessWidget {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: isUser gradient: isUser
? const LinearGradient( ? LinearGradient(
colors: [Color(0xFF82C9BD), Color(0xFF6BA5A0)], colors: [
extras.actionCardGradientStart,
extras.actionCardGradientEnd,
],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
) )
: LinearGradient( : LinearGradient(
colors: [ colors: [
Colors.white.withOpacity(0.95), cs.surfaceContainerHighest,
Colors.white.withOpacity(0.9), cs.surface,
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
@@ -154,24 +167,24 @@ class MessageBubble extends StatelessWidget {
data: content, data: content,
styleSheet: MarkdownStyleSheet( styleSheet: MarkdownStyleSheet(
p: TextStyle( p: TextStyle(
color: const Color(0xFF2D3748), color: cs.onSurface,
fontSize: 16, fontSize: 16,
height: 1.4, height: 1.4,
), ),
strong: TextStyle( strong: TextStyle(
color: const Color(0xFF2D3748), color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
height: 1.4, height: 1.4,
), ),
em: TextStyle( em: TextStyle(
color: const Color(0xFF2D3748), color: cs.onSurface,
fontSize: 16, fontSize: 16,
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
height: 1.4, height: 1.4,
), ),
listBullet: TextStyle( listBullet: TextStyle(
color: const Color(0xFF2D3748), color: cs.onSurface,
fontSize: 16, fontSize: 16,
height: 1.4, height: 1.4,
), ),
@@ -262,7 +275,7 @@ class MessageBubble extends StatelessWidget {
Icon( Icon(
Icons.menu_book, Icons.menu_book,
size: 14, size: 14,
color: const Color(0xFF82C9BD), color: Theme.of(context).colorScheme.primary,
), ),
const SizedBox(width: 6), const SizedBox(width: 6),
Expanded( Expanded(
@@ -280,7 +293,7 @@ class MessageBubble extends StatelessWidget {
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF82C9BD).withOpacity(0.1), color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: Text( child: Text(
@@ -288,7 +301,7 @@ class MessageBubble extends StatelessWidget {
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: const Color(0xFF82C9BD), color: Theme.of(context).colorScheme.primary,
), ),
), ),
), ),

View File

@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
import '../../../../l10n/app_localizations.dart'; import '../../../../l10n/app_localizations.dart';
import '../../../../core/services/auth_service.dart'; import '../../../../core/services/auth_service.dart';
import '../../../../core/services/session_service.dart'; import '../../../../core/services/session_service.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../../../../shared/presentation/widgets/custom_notification.dart'; import '../../../../shared/presentation/widgets/custom_notification.dart';
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
@@ -212,10 +213,14 @@ class _LoginPageState extends State<LoginPage> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: Theme.of(context).brightness == Brightness.dark
? AppThemeExtras.of(context).authBackgroundGradient
: [
Theme.of(context).colorScheme.background, Theme.of(context).colorScheme.background,
Theme.of(context).colorScheme.primary.withOpacity(0.1), Theme.of(context).colorScheme.primary
Theme.of(context).colorScheme.secondary.withOpacity(0.05), .withOpacity(0.1),
Theme.of(context).colorScheme.secondary
.withOpacity(0.05),
Theme.of(context).colorScheme.background, Theme.of(context).colorScheme.background,
], ],
), ),

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:go_router/go_router.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'; import '../../../../l10n/app_localizations.dart';
class RoleSelectionPage extends StatefulWidget { class RoleSelectionPage extends StatefulWidget {
@@ -22,7 +22,9 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: Theme.of(context).brightness == Brightness.dark
? AppThemeExtras.of(context).authBackgroundGradient
: [
Theme.of(context).colorScheme.background, Theme.of(context).colorScheme.background,
Theme.of(context).colorScheme.primary.withOpacity(0.1), Theme.of(context).colorScheme.primary.withOpacity(0.1),
Theme.of(context).colorScheme.secondary.withOpacity(0.05), Theme.of(context).colorScheme.secondary.withOpacity(0.05),
@@ -51,10 +53,12 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
width: 100, width: 100,
height: 100, height: 100,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: LinearGradient(
colors: [ colors: [
AppColors.gradientStart, AppThemeExtras.of(context)
AppColors.gradientEnd, .actionCardGradientStart,
AppThemeExtras.of(context)
.actionCardGradientEnd,
], ],
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
@@ -62,9 +66,10 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
borderRadius: BorderRadius.circular(25), borderRadius: BorderRadius.circular(25),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: AppColors.primaryBlue.withOpacity( color: Theme.of(context)
0.3, .colorScheme
), .primary
.withOpacity(0.3),
blurRadius: 25, blurRadius: 25,
offset: const Offset(0, 10), offset: const Offset(0, 10),
), ),
@@ -113,11 +118,10 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
const SizedBox(height: 12), const SizedBox(height: 12),
ShaderMask( ShaderMask(
shaderCallback: (bounds) => shaderCallback: (bounds) => LinearGradient(
const LinearGradient(
colors: [ colors: [
AppColors.primaryTeal, Theme.of(context).colorScheme.primary,
AppColors.primaryOrange, Theme.of(context).colorScheme.secondary,
], ],
begin: Alignment.centerLeft, begin: Alignment.centerLeft,
end: Alignment.centerRight, end: Alignment.centerRight,
@@ -172,7 +176,9 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
Text( Text(
'Selecione o seu papel para continuar', 'Selecione o seu papel para continuar',
style: Theme.of(context).textTheme.bodyLarge style: Theme.of(context).textTheme.bodyLarge
?.copyWith(color: AppColors.primaryOrange), ?.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
) )
.animate() .animate()
.fadeIn( .fadeIn(
@@ -196,7 +202,7 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
'Aluno', 'Aluno',
Icons.school_outlined, Icons.school_outlined,
'student', 'student',
AppColors.gradientStart, Theme.of(context).colorScheme.primary,
), ),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
@@ -206,7 +212,7 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
'Professor', 'Professor',
Icons.person_outline, Icons.person_outline,
'teacher', 'teacher',
AppColors.gradientEnd, Theme.of(context).colorScheme.secondary,
), ),
), ),
], ],
@@ -233,12 +239,15 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
? _handleContinue ? _handleContinue
: null, : null,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue, backgroundColor:
foregroundColor: Colors.white, Theme.of(context).colorScheme.primary,
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
elevation: 4, elevation: 4,
shadowColor: AppColors.primaryBlue.withOpacity( shadowColor: Theme.of(context)
0.3, .colorScheme
), .primary
.withOpacity(0.3),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
@@ -322,12 +331,14 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
end: Alignment.bottomRight, end: Alignment.bottomRight,
) )
: null, : null,
color: isSelected ? null : Colors.white, color: isSelected
? null
: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
border: Border.all( border: Border.all(
color: isSelected color: isSelected
? gradientColor ? gradientColor
: AppColors.primaryBlue.withOpacity(0.2), : Theme.of(context).colorScheme.primary.withOpacity(0.2),
width: isSelected ? 2 : 1, width: isSelected ? 2 : 1,
), ),
boxShadow: [ boxShadow: [
@@ -346,7 +357,9 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
Icon( Icon(
icon, icon,
size: 48, size: 48,
color: isSelected ? Colors.white : AppColors.primaryBlue, color: isSelected
? Colors.white
: Theme.of(context).colorScheme.primary,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
@@ -386,7 +399,7 @@ class _RoleSelectionPageState extends State<RoleSelectionPage> {
width: 4, width: 4,
height: 4, height: 4,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.gradientStart.withOpacity(0.3), color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
) )

View File

@@ -4,6 +4,7 @@ import 'package:flutter_animate/flutter_animate.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../../../l10n/app_localizations.dart'; import '../../../../l10n/app_localizations.dart';
import '../../../../core/services/auth_service.dart'; import '../../../../core/services/auth_service.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../../../../shared/presentation/widgets/custom_notification.dart'; import '../../../../shared/presentation/widgets/custom_notification.dart';
class SignupPage extends StatefulWidget { class SignupPage extends StatefulWidget {
@@ -241,10 +242,14 @@ class _SignupPageState extends State<SignupPage> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: Theme.of(context).brightness == Brightness.dark
? AppThemeExtras.of(context).authBackgroundGradient
: [
Theme.of(context).colorScheme.background, Theme.of(context).colorScheme.background,
Theme.of(context).colorScheme.primary.withOpacity(0.1), Theme.of(context).colorScheme.primary
Theme.of(context).colorScheme.secondary.withOpacity(0.05), .withOpacity(0.1),
Theme.of(context).colorScheme.secondary
.withOpacity(0.05),
Theme.of(context).colorScheme.background, Theme.of(context).colorScheme.background,
], ],
), ),
@@ -364,7 +369,7 @@ class _SignupPageState extends State<SignupPage> {
).colorScheme.onSurface, ).colorScheme.onSurface,
), ),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Nome Completo', labelText: 'Primeiro Nome',
labelStyle: TextStyle( labelStyle: TextStyle(
color: Theme.of( color: Theme.of(
context, context,

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../../../core/services/auth_service.dart'; import '../../../../core/services/auth_service.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../widgets/progress_hero_widget.dart'; import '../widgets/progress_hero_widget.dart';
import '../widgets/quick_access_widget.dart'; import '../widgets/quick_access_widget.dart';
import '../widgets/student_classes_list_widget.dart'; import '../widgets/student_classes_list_widget.dart';
@@ -83,19 +84,17 @@ class _StudentDashboardPageState extends State<StudentDashboardPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final themeExtras = AppThemeExtras.of(context);
final headerColor = themeExtras.dashboardHeaderTextColor;
return Scaffold( return Scaffold(
body: Container( body: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: themeExtras.dashboardBackgroundGradient,
Theme.of(context).colorScheme.primary, stops: themeExtras.dashboardGradientStops,
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],
), ),
), ),
child: SafeArea( child: SafeArea(
@@ -114,17 +113,17 @@ class _StudentDashboardPageState extends State<StudentDashboardPage> {
children: [ children: [
Text( Text(
'Bem-vindo, $_userName!', 'Bem-vindo, $_userName!',
style: const TextStyle( style: TextStyle(
color: Colors.white, color: headerColor,
fontSize: 28, fontSize: 28,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( Text(
'Seu progresso de estudos', 'Seu progresso de estudos',
style: TextStyle( style: TextStyle(
color: Colors.white, color: headerColor,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
), ),
@@ -133,7 +132,7 @@ class _StudentDashboardPageState extends State<StudentDashboardPage> {
), ),
), ),
IconButton( IconButton(
icon: const Icon(Icons.logout, color: Colors.white), icon: Icon(Icons.logout, color: headerColor),
onPressed: () async { onPressed: () async {
await AuthService.signOut(); await AuthService.signOut();
if (mounted) { if (mounted) {

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../../../core/services/auth_service.dart'; import '../../../../core/services/auth_service.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../widgets/teacher_hero_widget.dart'; import '../widgets/teacher_hero_widget.dart';
import '../widgets/teacher_quick_actions_widget.dart'; import '../widgets/teacher_quick_actions_widget.dart';
import '../widgets/teacher_classes_list_widget.dart'; import '../widgets/teacher_classes_list_widget.dart';
@@ -79,19 +80,17 @@ class _TeacherDashboardPageState extends State<TeacherDashboardPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final themeExtras = AppThemeExtras.of(context);
final headerColor = themeExtras.dashboardHeaderTextColor;
return Scaffold( return Scaffold(
body: Container( body: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topCenter, begin: Alignment.topCenter,
end: Alignment.bottomCenter, end: Alignment.bottomCenter,
colors: [ colors: themeExtras.dashboardBackgroundGradient,
Theme.of(context).colorScheme.primary, stops: themeExtras.dashboardGradientStops,
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],
), ),
), ),
child: SafeArea( child: SafeArea(
@@ -110,17 +109,17 @@ class _TeacherDashboardPageState extends State<TeacherDashboardPage> {
children: [ children: [
Text( Text(
'Bem-vindo, $_userName!', 'Bem-vindo, $_userName!',
style: const TextStyle( style: TextStyle(
color: Colors.white, color: headerColor,
fontSize: 28, fontSize: 28,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( Text(
'Painel de Gestão de Conteúdo', 'Painel de Gestão de Conteúdo',
style: TextStyle( style: TextStyle(
color: Colors.white, color: headerColor,
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
), ),
@@ -129,7 +128,7 @@ class _TeacherDashboardPageState extends State<TeacherDashboardPage> {
), ),
), ),
IconButton( IconButton(
icon: const Icon(Icons.logout, color: Colors.white), icon: Icon(Icons.logout, color: headerColor),
onPressed: () async { onPressed: () async {
await AuthService.signOut(); await AuthService.signOut();
if (mounted) { if (mounted) {

View File

@@ -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,
);
}
}

View File

@@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../core/theme/app_theme_extension.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import '../../../../core/services/auth_service.dart'; import '../../../../core/services/auth_service.dart';
import '../../../../core/routing/app_router.dart'; import '../../../../core/routing/app_router.dart';
@@ -42,10 +44,8 @@ class ProfileSectionWidget extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
Theme.of(context).colorScheme.primary, AppThemeExtras.of(context).actionCardGradientStart,
Theme.of( AppThemeExtras.of(context).actionCardGradientEnd,
context,
).colorScheme.primary.withOpacity(0.8),
], ],
), ),
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import '../../../../core/theme/app_theme_extension.dart';
/// Progress tracking hero section for student dashboard /// Progress tracking hero section for student dashboard
class ProgressHeroWidget extends StatelessWidget { class ProgressHeroWidget extends StatelessWidget {
final String userName; final String userName;
@@ -148,8 +150,11 @@ class ProgressHeroWidget extends StatelessWidget {
widthFactor: overallProgress, widthFactor: overallProgress,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: LinearGradient(
colors: [Colors.white, Color(0xFFF8F9FA)], colors: [
AppThemeExtras.of(context).heroProgressStart,
AppThemeExtras.of(context).heroProgressEnd,
],
), ),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),

View File

@@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../../classes/presentation/pages/join_class_page.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 { class QuickAccessWidget extends StatelessWidget {
const QuickAccessWidget({super.key}); const QuickAccessWidget({super.key});
@@ -21,18 +22,23 @@ class QuickAccessWidget extends StatelessWidget {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
IntrinsicHeight(
Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// Tutor IA Card (Primary) Expanded(
Expanded(flex: 3, child: _buildTutorIACard(context)), flex: 3,
child: _buildTutorIACard(context),
),
const SizedBox(width: 16), const SizedBox(width: 16),
// Quiz Card (Secondary) Expanded(
Expanded(flex: 2, child: _buildQuizCard(context)), flex: 2,
child: _buildQuizCard(context),
),
], ],
), ),
),
const SizedBox(height: 16), const SizedBox(height: 16),
// Join Class Card
_buildJoinClassCard(context), _buildJoinClassCard(context),
], ],
) )
@@ -45,110 +51,13 @@ class QuickAccessWidget extends StatelessWidget {
} }
Widget _buildTutorIACard(BuildContext context) { Widget _buildTutorIACard(BuildContext context) {
return Container( return DashboardActionCard(
height: 150, title: 'Tutor IA',
decoration: BoxDecoration( subtitle: 'Assistente de estudos',
gradient: LinearGradient( icon: Icons.psychology,
begin: Alignment.topLeft, useGradient: true,
end: Alignment.bottomRight, minHeight: 150,
colors: [ onTap: () => context.go('/ai-tutor'),
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,
),
),
],
),
],
),
),
),
),
) )
.animate() .animate()
.scale( .scale(
@@ -159,80 +68,14 @@ class QuickAccessWidget extends StatelessWidget {
} }
Widget _buildQuizCard(BuildContext context) { Widget _buildQuizCard(BuildContext context) {
return Container( final cs = Theme.of(context).colorScheme;
height: 150, return DashboardActionCardSurface(
decoration: BoxDecoration( title: 'Quiz',
color: Theme.of(context).colorScheme.surface, subtitle: 'Testa os teus conhecimentos',
borderRadius: BorderRadius.circular(16), icon: Icons.quiz,
border: Border.all( minHeight: 150,
color: Theme.of(context).colorScheme.outline.withOpacity(0.2), iconColor: cs.secondary,
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'), 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,
),
),
],
),
],
),
),
),
),
) )
.animate() .animate()
.scale( .scale(
@@ -243,90 +86,18 @@ class QuickAccessWidget extends StatelessWidget {
} }
Widget _buildJoinClassCard(BuildContext context) { Widget _buildJoinClassCard(BuildContext context) {
return Container( return DashboardActionCard(
height: 86, title: 'Entrar numa Turma',
decoration: BoxDecoration( subtitle: 'Junta-te a uma turma com o código',
color: Theme.of(context).colorScheme.surface, icon: Icons.group_add,
borderRadius: BorderRadius.circular(16), layout: DashboardActionCardLayout.horizontal,
border: Border.all( minHeight: 0,
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: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute(builder: (_) => const JoinClassPage()), 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,
),
],
),
),
),
),
) )
.animate() .animate()
.scale( .scale(

View File

@@ -107,7 +107,7 @@ class StudentClassesListWidget extends StatelessWidget {
if (!snapshot.hasData || !snapshot.data!.exists) { if (!snapshot.hasData || !snapshot.data!.exists) {
return Container( return Container(
width: 200, width: 200,
height: 150, constraints: const BoxConstraints(minHeight: 150),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
@@ -135,7 +135,7 @@ class StudentClassesListWidget extends StatelessWidget {
return Container( return Container(
width: 200, width: 200,
height: 150, constraints: const BoxConstraints(minHeight: 150),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
@@ -172,7 +172,7 @@ class StudentClassesListWidget extends StatelessWidget {
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),

View File

@@ -1,4 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../../core/theme/app_theme_extension.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import '../../../../core/services/auth_service.dart'; import '../../../../core/services/auth_service.dart';
@@ -41,10 +43,8 @@ class TeacherAnalyticsPreviewWidget extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
colors: [ colors: [
Theme.of(context).colorScheme.primary, AppThemeExtras.of(context).actionCardGradientStart,
Theme.of( AppThemeExtras.of(context).actionCardGradientEnd,
context,
).colorScheme.primary.withOpacity(0.8),
], ],
), ),
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
@@ -288,8 +288,7 @@ class TeacherAnalyticsPreviewWidget extends StatelessWidget {
label, label,
style: TextStyle(color: color.withOpacity(0.8), fontSize: 10), style: TextStyle(color: color.withOpacity(0.8), fontSize: 10),
textAlign: TextAlign.center, textAlign: TextAlign.center,
maxLines: 2, maxLines: 3,
overflow: TextOverflow.ellipsis,
), ),
], ],
), ),

View File

@@ -112,7 +112,7 @@ class TeacherClassesListWidget extends StatelessWidget {
}, },
child: Container( child: Container(
width: 200, width: 200,
height: 150, constraints: const BoxConstraints(minHeight: 150),
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
@@ -149,7 +149,7 @@ class TeacherClassesListWidget extends StatelessWidget {
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
import '../../../../core/theme/app_theme_extension.dart';
/// Hero section for teacher dashboard showing class overview /// Hero section for teacher dashboard showing class overview
class TeacherHeroWidget extends StatelessWidget { class TeacherHeroWidget extends StatelessWidget {
final String userName; final String userName;
@@ -34,8 +36,6 @@ class TeacherHeroWidget extends StatelessWidget {
children: [ children: [
Text( Text(
'Visão Geral da Turma', 'Visão Geral da Turma',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
fontSize: 20, fontSize: 20,
@@ -45,8 +45,6 @@ class TeacherHeroWidget extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
'Acompanhe o progresso dos seus alunos', 'Acompanhe o progresso dos seus alunos',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant, color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 13, fontSize: 13,
@@ -148,8 +146,11 @@ class TeacherHeroWidget extends StatelessWidget {
widthFactor: classAverageProgress, widthFactor: classAverageProgress,
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: LinearGradient(
colors: [Colors.white, Color(0xFFF8F9FA)], colors: [
AppThemeExtras.of(context).heroProgressStart,
AppThemeExtras.of(context).heroProgressEnd,
],
), ),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),

View File

@@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart';
import '../../../../core/services/auth_service.dart'; import '../../../../core/services/auth_service.dart';
import '../../../../features/materials/presentation/pages/teacher_materials_page.dart'; import '../../../../features/materials/presentation/pages/teacher_materials_page.dart';
import 'dashboard_action_card.dart';
/// Quick access cards for teacher actions /// Quick access cards for teacher actions
class TeacherQuickActionsWidget extends StatefulWidget { class TeacherQuickActionsWidget extends StatefulWidget {
@@ -20,8 +21,25 @@ class TeacherQuickActionsWidget extends StatefulWidget {
class _TeacherQuickActionsWidgetState extends State<TeacherQuickActionsWidget> { class _TeacherQuickActionsWidgetState extends State<TeacherQuickActionsWidget> {
bool _isCreatingClass = false; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cards = [
_buildUploadContentCard(context),
_buildCreateClassCard(context),
_buildCreateQuizCard(context),
_buildViewAnalyticsCard(context),
];
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -33,29 +51,20 @@ class _TeacherQuickActionsWidgetState extends State<TeacherQuickActionsWidget> {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 12),
SizedBox(
// Primary Actions Row height: _scrollRowHeight,
Row( child: ListView.separated(
children: [ scrollDirection: Axis.horizontal,
// Upload Content Card (Primary) clipBehavior: Clip.none,
Expanded(flex: 2, child: _buildUploadContentCard(context)), padding: const EdgeInsets.only(right: 16),
const SizedBox(width: 16), itemCount: cards.length,
// Create Class Card separatorBuilder: (_, __) => const SizedBox(width: 12),
Expanded(flex: 2, child: _buildCreateClassCard(context)), itemBuilder: (context, index) => SizedBox(
const SizedBox(width: 16), width: _scrollCardWidth,
// Create Quiz Card (Secondary) child: cards[index],
Expanded(flex: 2, child: _buildCreateQuizCard(context)), ),
],
), ),
const SizedBox(height: 16),
// Secondary Actions Row
Row(
children: [
// View Analytics Card
Expanded(child: _buildViewAnalyticsCard(context)),
],
), ),
], ],
) )
@@ -70,111 +79,23 @@ class _TeacherQuickActionsWidgetState extends State<TeacherQuickActionsWidget> {
Widget _buildUploadContentCard(BuildContext context) { Widget _buildUploadContentCard(BuildContext context) {
return ClipRRect( return ClipRRect(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: child: DashboardActionCard(
Container( title: 'Upload Conteúdo',
height: 150, subtitle: 'PDFs, textos, imagens',
decoration: BoxDecoration( icon: Icons.upload_file,
gradient: LinearGradient( useGradient: true,
begin: Alignment.topLeft, minHeight: _cardMinHeight,
end: Alignment.bottomRight, iconSize: _iconSize,
colors: [ iconPadding: _iconPadding,
Theme.of(context).colorScheme.primary, titleFontSize: _titleFontSize,
Theme.of(context).colorScheme.primary.withOpacity(0.8), subtitleFontSize: _subtitleFontSize,
], padding: _cardPadding,
),
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( onTap: () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => const TeacherMaterialsPage(), 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() .animate()
.scale( .scale(
@@ -188,86 +109,16 @@ class _TeacherQuickActionsWidgetState extends State<TeacherQuickActionsWidget> {
Widget _buildCreateQuizCard(BuildContext context) { Widget _buildCreateQuizCard(BuildContext context) {
return ClipRRect( return ClipRRect(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: child: DashboardActionCardSurface(
Container( title: 'Criar Quiz',
height: 150, subtitle: 'Avaliações interativas',
decoration: BoxDecoration( icon: Icons.quiz,
color: Theme.of(context).colorScheme.surface, minHeight: _cardMinHeight,
borderRadius: BorderRadius.circular(16), titleFontSize: _titleFontSize,
border: Border.all( subtitleFontSize: _subtitleFontSize,
color: Theme.of( iconSize: _iconSize,
context, padding: _cardPadding,
).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'), 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() .animate()
.scale( .scale(
@@ -279,75 +130,26 @@ class _TeacherQuickActionsWidgetState extends State<TeacherQuickActionsWidget> {
} }
Widget _buildViewAnalyticsCard(BuildContext context) { Widget _buildViewAnalyticsCard(BuildContext context) {
return Container( final cs = Theme.of(context).colorScheme;
height: 120, 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( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: cs.primary.withOpacity(0.1),
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), borderRadius: BorderRadius.circular(10),
), ),
child: Icon( child: Icon(Icons.analytics, color: cs.primary, size: _iconSize),
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,
),
),
],
),
],
),
),
),
), ),
onTap: () => context.go('/teacher/analytics'),
) )
.animate() .animate()
.scale( .scale(
@@ -358,102 +160,37 @@ class _TeacherQuickActionsWidgetState extends State<TeacherQuickActionsWidget> {
} }
Widget _buildCreateClassCard(BuildContext context) { Widget _buildCreateClassCard(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return ClipRRect( return ClipRRect(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
child: child: DashboardActionCardSurface(
Container( title: 'Criar Turma',
height: 150, 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( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: cs.primary.withOpacity(0.1),
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), borderRadius: BorderRadius.circular(10),
), ),
child: _isCreatingClass child: _isCreatingClass
? SizedBox( ? SizedBox(
width: 20, width: _iconSize,
height: 20, height: _iconSize,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2, strokeWidth: 2,
color: Theme.of( color: cs.primary,
context,
).colorScheme.primary,
), ),
) )
: Icon( : Icon(Icons.school, color: cs.primary, size: _iconSize),
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,
),
),
],
),
],
),
),
),
), ),
) )
.animate() .animate()

View File

@@ -121,8 +121,8 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
Text( Text(
'Erro ao carregar materiais:\n${snapshot.error}', 'Erro ao carregar materiais:\n${snapshot.error}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle( style: TextStyle(
color: Color(0xFF2D3748), color: Theme.of(context).colorScheme.onSurface,
fontSize: 16, fontSize: 16,
), ),
), ),
@@ -134,28 +134,28 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
final materials = snapshot.data?.docs ?? []; final materials = snapshot.data?.docs ?? [];
if (materials.isEmpty) { if (materials.isEmpty) {
return const Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.folder_open, Icons.folder_open,
color: Color(0xFF718096), color: Theme.of(context).colorScheme.onSurfaceVariant,
size: 64, size: 64,
), ),
SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
'Nenhum material enviado ainda.', 'Nenhum material enviado ainda.',
style: TextStyle( style: TextStyle(
color: Color(0xFF718096), color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 16, fontSize: 16,
), ),
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Os materiais enviados aparecerão aqui.', 'Os materiais enviados aparecerão aqui.',
style: TextStyle( style: TextStyle(
color: Color(0xFF9CA3AF), color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 14, fontSize: 14,
), ),
), ),
@@ -226,12 +226,12 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
const Text( Text(
'Adicionar Material', 'Adicionar Material',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Color(0xFF2D3748), color: Theme.of(context).colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@@ -318,10 +318,10 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
children: [ children: [
Text( Text(
title, title,
style: const TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Color(0xFF2D3748), color: Theme.of(context).colorScheme.onSurface,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
@@ -786,12 +786,12 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
), ),
title: Text( title: Text(
fileName, fileName,
style: const TextStyle( style: TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 16, fontSize: 16,
color: Color(0xFF2D3748), color: Theme.of(context).colorScheme.onSurface,
), ),
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
subtitle: classId != null subtitle: classId != null
@@ -805,8 +805,8 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
children: [ children: [
Text( Text(
formattedDate, formattedDate,
style: const TextStyle( style: TextStyle(
color: Color(0xFF718096), color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 13, fontSize: 13,
), ),
), ),
@@ -814,21 +814,21 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
const SizedBox(height: 2), const SizedBox(height: 2),
Row( Row(
children: [ children: [
const Icon( Icon(
Icons.school_outlined, Icons.school_outlined,
size: 12, size: 12,
color: Color(0xFF82C9BD), color: Theme.of(context).colorScheme.primary,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Flexible( Flexible(
child: Text( child: Text(
className, className,
style: const TextStyle( style: TextStyle(
color: Color(0xFF82C9BD), color: Theme.of(context).colorScheme.primary,
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
@@ -841,8 +841,8 @@ class _TeacherMaterialsPageState extends State<TeacherMaterialsPage> {
) )
: Text( : Text(
formattedDate, formattedDate,
style: const TextStyle( style: TextStyle(
color: Color(0xFF718096), color: Theme.of(context).colorScheme.onSurfaceVariant,
fontSize: 13, fontSize: 13,
), ),
), ),

View File

@@ -646,7 +646,7 @@ class _InteractiveQuizSheetState extends State<_InteractiveQuizSheet> {
children: [ children: [
Text( Text(
widget.title, widget.title,
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: cs.onSurface), style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: cs.onSurface),
), ),
@@ -994,7 +994,7 @@ class _TeacherQuizInteractiveSheetState extends State<_TeacherQuizInteractiveShe
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(widget.title, Text(widget.title,
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: cs.onSurface)), style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: cs.onSurface)),
if (!_submitted) if (!_submitted)

View File

@@ -504,6 +504,7 @@ class _QuizEditorPageState extends State<_QuizEditorPage> {
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
widget.materialName.replaceAll('.pdf', '').replaceAll('_', ' '), widget.materialName.replaceAll('.pdf', '').replaceAll('_', ' '),
maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
backgroundColor: cs.surface, backgroundColor: cs.surface,
@@ -693,7 +694,7 @@ class _QuestionEditorState extends State<_QuestionEditor> {
Expanded( Expanded(
child: Text( child: Text(
widget.question.question.isEmpty ? 'Pergunta ${widget.index + 1}' : widget.question.question, widget.question.question.isEmpty ? 'Pergunta ${widget.index + 1}' : widget.question.question,
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: cs.onSurface), style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: cs.onSurface),
), ),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../../../core/theme/app_colors.dart'; import '../../../../core/theme/app_colors.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../../../../core/services/theme_service.dart'; import '../../../../core/services/theme_service.dart';
import '../../../../core/providers/theme_provider.dart'; import '../../../../core/providers/theme_provider.dart';
@@ -34,16 +35,12 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: themeMode == ThemeMode.light colors: themeMode == ThemeMode.light
? [ ? const [
const Color(0xFFD4E8E8), Color(0xFFD4E8E8),
const Color(0xFFE8D4C0), Color(0xFFE8D4C0),
const Color(0xFFD8E0E8), Color(0xFFD8E0E8),
] ]
: [ : AppThemeExtras.of(context).authBackgroundGradient,
Theme.of(context).colorScheme.primary.withOpacity(0.1),
Theme.of(context).colorScheme.secondary.withOpacity(0.05),
Theme.of(context).colorScheme.background,
],
), ),
), ),
child: SafeArea( child: SafeArea(
@@ -55,7 +52,10 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
child: Row( child: Row(
children: [ children: [
IconButton( 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'), onPressed: () => context.go('/student-dashboard'),
), ),
Expanded( Expanded(

View File

@@ -3,6 +3,7 @@ import 'package:flutter_animate/flutter_animate.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../../../../core/services/auth_service.dart'; import '../../../../core/services/auth_service.dart';
import '../../../../core/services/session_service.dart'; import '../../../../core/services/session_service.dart';
import '../../../../core/theme/app_theme_extension.dart';
import '../../../../l10n/app_localizations.dart'; import '../../../../l10n/app_localizations.dart';
class SplashPage extends StatefulWidget { class SplashPage extends StatefulWidget {
@@ -117,7 +118,9 @@ class _SplashPageState extends State<SplashPage> {
gradient: LinearGradient( gradient: LinearGradient(
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: Theme.of(context).brightness == Brightness.dark
? AppThemeExtras.of(context).authBackgroundGradient
: [
Theme.of(context).colorScheme.background, Theme.of(context).colorScheme.background,
Theme.of(context).colorScheme.primary.withOpacity(0.1), Theme.of(context).colorScheme.primary.withOpacity(0.1),
Theme.of(context).colorScheme.secondary.withOpacity(0.05), Theme.of(context).colorScheme.secondary.withOpacity(0.05),