Telas de login e dashboard de estudante feito
This commit is contained in:
60
lib/core/constants/app_constants.dart
Normal file
60
lib/core/constants/app_constants.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
class AppConstants {
|
||||
// App Info
|
||||
static const String appName = 'AI Study Assistant';
|
||||
static const String appVersion = '1.0.0';
|
||||
|
||||
// Firebase Configuration
|
||||
static const String firebaseProjectId = 'teachit-dev-12345';
|
||||
|
||||
// API Configuration
|
||||
static const String apiBaseUrl = 'http://localhost:5001';
|
||||
static const String apiVersion = 'v1';
|
||||
|
||||
// Storage Keys
|
||||
static const String userTokenKey = 'user_token';
|
||||
static const String userPreferencesKey = 'user_preferences';
|
||||
static const String themeKey = 'theme_mode';
|
||||
|
||||
// Animation Durations
|
||||
static const Duration shortAnimation = Duration(milliseconds: 200);
|
||||
static const Duration mediumAnimation = Duration(milliseconds: 300);
|
||||
static const Duration longAnimation = Duration(milliseconds: 500);
|
||||
|
||||
// Spacing
|
||||
static const double spacingXS = 4.0;
|
||||
static const double spacingS = 8.0;
|
||||
static const double spacingM = 16.0;
|
||||
static const double spacingL = 24.0;
|
||||
static const double spacingXL = 32.0;
|
||||
|
||||
// Border Radius
|
||||
static const double radiusXS = 4.0;
|
||||
static const double radiusS = 8.0;
|
||||
static const double radiusM = 12.0;
|
||||
static const double radiusL = 16.0;
|
||||
static const double radiusXL = 20.0;
|
||||
|
||||
// Screen Breakpoints
|
||||
static const double mobileBreakpoint = 600.0;
|
||||
static const double tabletBreakpoint = 1024.0;
|
||||
static const double desktopBreakpoint = 1440.0;
|
||||
|
||||
// Pagination
|
||||
static const int defaultPageSize = 20;
|
||||
static const int maxPageSize = 100;
|
||||
|
||||
// File Upload Limits
|
||||
static const int maxFileSize = 50 * 1024 * 1024; // 50MB
|
||||
static const List<String> allowedFileTypes = [
|
||||
'pdf', 'doc', 'docx', 'txt', 'jpg', 'jpeg', 'png', 'gif', 'mp4', 'avi', 'mov'
|
||||
];
|
||||
|
||||
// Chat Configuration
|
||||
static const int maxMessageLength = 500;
|
||||
static const int maxChatHistory = 50;
|
||||
|
||||
// Quiz Configuration
|
||||
static const int defaultQuizTimeLimit = 30; // minutes
|
||||
static const int maxQuestionsPerQuiz = 50;
|
||||
static const double passingScore = 0.7; // 70%
|
||||
}
|
||||
177
lib/core/routing/app_router.dart
Normal file
177
lib/core/routing/app_router.dart
Normal file
@@ -0,0 +1,177 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../features/auth/presentation/pages/login_page.dart';
|
||||
import '../../features/auth/presentation/pages/signup_page.dart';
|
||||
import '../../features/dashboard/presentation/pages/student_dashboard_page.dart';
|
||||
import '../../features/dashboard/presentation/pages/teacher_dashboard_page.dart';
|
||||
import '../../features/tutor/presentation/pages/tutor_chat_page.dart';
|
||||
import '../../features/quiz/presentation/pages/quiz_list_page.dart';
|
||||
import '../../features/quiz/presentation/pages/quiz_page.dart';
|
||||
import '../../features/profile/presentation/pages/profile_page.dart';
|
||||
import '../../features/splash/presentation/pages/splash_page.dart';
|
||||
import '../../features/auth/presentation/pages/role_selection_page.dart';
|
||||
import '../../shared/presentation/pages/not_found_page.dart';
|
||||
|
||||
/// App Router Configuration
|
||||
class AppRouter {
|
||||
static const String splash = '/splash';
|
||||
static const String roleSelection = '/role-selection';
|
||||
static const String login = '/login';
|
||||
static const String signup = '/signup';
|
||||
static const String studentDashboard = '/student-dashboard';
|
||||
static const String teacherDashboard = '/teacher-dashboard';
|
||||
static const String tutor = '/tutor';
|
||||
static const String quizList = '/quiz';
|
||||
static const String quiz = '/quiz/:quizId';
|
||||
static const String profile = '/profile';
|
||||
|
||||
// Nested route paths (without leading slash)
|
||||
static const String tutorNested = 'tutor';
|
||||
static const String quizListNested = 'quiz';
|
||||
static const String quizNested = 'quiz/:quizId';
|
||||
|
||||
static final GoRouter router = GoRouter(
|
||||
initialLocation: splash,
|
||||
debugLogDiagnostics: true,
|
||||
errorBuilder: (context, state) => const NotFoundPage(),
|
||||
|
||||
routes: [
|
||||
// Splash Screen
|
||||
GoRoute(
|
||||
path: splash,
|
||||
name: 'splash',
|
||||
builder: (context, state) => const SplashPage(),
|
||||
),
|
||||
|
||||
// Role Selection
|
||||
GoRoute(
|
||||
path: roleSelection,
|
||||
name: 'roleSelection',
|
||||
builder: (context, state) => const RoleSelectionPage(),
|
||||
),
|
||||
|
||||
// Authentication Routes
|
||||
GoRoute(
|
||||
path: login,
|
||||
name: 'login',
|
||||
builder: (context, state) => const LoginPage(),
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
path: signup,
|
||||
name: 'signup',
|
||||
builder: (context, state) => const SignupPage(),
|
||||
),
|
||||
|
||||
// Dashboard Routes
|
||||
GoRoute(
|
||||
path: studentDashboard,
|
||||
name: 'studentDashboard',
|
||||
builder: (context, state) => const StudentDashboardPage(),
|
||||
routes: [
|
||||
// Nested routes for student features
|
||||
GoRoute(
|
||||
path: tutorNested,
|
||||
name: 'studentTutor',
|
||||
builder: (context, state) => const TutorChatPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: quizListNested,
|
||||
name: 'quizList',
|
||||
builder: (context, state) => const QuizListPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: quizNested,
|
||||
name: 'quiz',
|
||||
builder: (context, state) {
|
||||
final quizId = state.pathParameters['quizId']!;
|
||||
return QuizPage(quizId: quizId);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
GoRoute(
|
||||
path: teacherDashboard,
|
||||
name: 'teacherDashboard',
|
||||
builder: (context, state) => const TeacherDashboardPage(),
|
||||
routes: [
|
||||
// Nested routes for teacher features
|
||||
GoRoute(
|
||||
path: tutorNested,
|
||||
name: 'teacherTutor',
|
||||
builder: (context, state) => const TutorChatPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: quizListNested,
|
||||
name: 'teacherQuizList',
|
||||
builder: (context, state) => const QuizListPage(),
|
||||
),
|
||||
GoRoute(
|
||||
path: quizNested,
|
||||
name: 'teacherQuiz',
|
||||
builder: (context, state) {
|
||||
final quizId = state.pathParameters['quizId']!;
|
||||
return QuizPage(quizId: quizId);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Profile Route
|
||||
GoRoute(
|
||||
path: profile,
|
||||
name: 'profile',
|
||||
builder: (context, state) => const ProfilePage(),
|
||||
),
|
||||
],
|
||||
|
||||
// Redirect unauthenticated users to login
|
||||
redirect: (context, state) {
|
||||
// TODO: Implement authentication check
|
||||
// For now, allow all routes
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
// Navigation helpers
|
||||
static void goToLogin(BuildContext context) {
|
||||
context.go(login);
|
||||
}
|
||||
|
||||
static void goToSignup(BuildContext context) {
|
||||
context.go(signup);
|
||||
}
|
||||
|
||||
static void goToStudentDashboard(BuildContext context) {
|
||||
context.go(studentDashboard);
|
||||
}
|
||||
|
||||
static void goToTeacherDashboard(BuildContext context) {
|
||||
context.go(teacherDashboard);
|
||||
}
|
||||
|
||||
static void goToTutor(BuildContext context) {
|
||||
context.go(tutor);
|
||||
}
|
||||
|
||||
static void goToQuizList(BuildContext context) {
|
||||
context.go(quizList);
|
||||
}
|
||||
|
||||
static void goToQuiz(BuildContext context, String quizId) {
|
||||
context.go('$quiz/$quizId');
|
||||
}
|
||||
|
||||
static void goToProfile(BuildContext context) {
|
||||
context.go(profile);
|
||||
}
|
||||
|
||||
static void goBack(BuildContext context) {
|
||||
context.pop();
|
||||
}
|
||||
|
||||
static void replaceWith(BuildContext context, String location) {
|
||||
context.go(location);
|
||||
}
|
||||
}
|
||||
128
lib/core/services/auth_service.dart
Normal file
128
lib/core/services/auth_service.dart
Normal file
@@ -0,0 +1,128 @@
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
|
||||
/// Service for handling Firebase Authentication
|
||||
class AuthService {
|
||||
static final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
|
||||
/// Get current user
|
||||
static User? get currentUser {
|
||||
return _auth.currentUser;
|
||||
}
|
||||
|
||||
/// Get auth state changes stream
|
||||
static Stream<User?> get authStateChanges {
|
||||
return _auth.authStateChanges();
|
||||
}
|
||||
|
||||
/// Sign up with email and password
|
||||
static Future<UserCredential?> signUpWithEmailAndPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
print('DEBUG: Tentando criar conta para email: $email');
|
||||
print('DEBUG: Password length: ${password.length}');
|
||||
|
||||
UserCredential result = await _auth.createUserWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
print('DEBUG: Conta criada com sucesso para: ${result.user?.email}');
|
||||
print('DEBUG: User ID: ${result.user?.uid}');
|
||||
print('DEBUG: Email verified: ${result.user?.emailVerified}');
|
||||
|
||||
// Verificar se o email foi verificado
|
||||
if (result.user != null && !result.user!.emailVerified) {
|
||||
print('DEBUG: Email não verificado, tentando enviar verificação...');
|
||||
await result.user!.sendEmailVerification();
|
||||
print('DEBUG: Email de verificação enviado');
|
||||
}
|
||||
|
||||
return result;
|
||||
} on FirebaseAuthException catch (e) {
|
||||
print('DEBUG: Erro Firebase ao criar conta: ${e.code} - ${e.message}');
|
||||
String errorMessage = _getErrorMessage(e.code);
|
||||
print('DEBUG: Mensagem de erro: $errorMessage');
|
||||
throw Exception(errorMessage);
|
||||
} catch (e) {
|
||||
print('DEBUG: Erro genérico ao criar conta: $e');
|
||||
throw Exception('Ocorreu um problema. Tente novamente');
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign in with email and password
|
||||
static Future<UserCredential?> signInWithEmailAndPassword({
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
try {
|
||||
print('DEBUG: Tentando login para email: $email');
|
||||
print('DEBUG: Password length: ${password.length}');
|
||||
|
||||
// Verificar se há usuário atual
|
||||
User? currentUser = _auth.currentUser;
|
||||
print('DEBUG: Usuário atual: ${currentUser?.email}');
|
||||
|
||||
UserCredential result = await _auth.signInWithEmailAndPassword(
|
||||
email: email,
|
||||
password: password,
|
||||
);
|
||||
|
||||
print('DEBUG: Login realizado com sucesso para: ${result.user?.email}');
|
||||
print('DEBUG: User ID: ${result.user?.uid}');
|
||||
print('DEBUG: Email verified: ${result.user?.emailVerified}');
|
||||
|
||||
return result;
|
||||
} on FirebaseAuthException catch (e) {
|
||||
print('DEBUG: Erro Firebase ao fazer login: ${e.code} - ${e.message}');
|
||||
String errorMessage = _getErrorMessage(e.code);
|
||||
print('DEBUG: Mensagem de erro: $errorMessage');
|
||||
throw Exception(errorMessage);
|
||||
} catch (e) {
|
||||
print('DEBUG: Erro genérico ao fazer login: $e');
|
||||
throw Exception('Ocorreu um problema. Tente novamente');
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign out
|
||||
static Future<void> signOut() async {
|
||||
try {
|
||||
print('DEBUG: Tentando fazer logout');
|
||||
await _auth.signOut();
|
||||
print('DEBUG: Logout realizado com sucesso');
|
||||
} catch (e) {
|
||||
print('DEBUG: Erro ao fazer logout: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get user-friendly error message
|
||||
static String _getErrorMessage(String code) {
|
||||
print('DEBUG: Processando código de erro: $code');
|
||||
|
||||
switch (code) {
|
||||
case 'weak-password':
|
||||
return 'A palavra-passe é muito fraca. Use pelo menos 8 caracteres.';
|
||||
case 'invalid-email':
|
||||
return 'O email fornecido é inválido. Verifique o formato.';
|
||||
case 'user-disabled':
|
||||
return 'Esta conta foi desativada. Contacte o suporte.';
|
||||
case 'user-not-found':
|
||||
return 'Nenhum utilizador encontrado com este email. Verifique os dados.';
|
||||
case 'wrong-password':
|
||||
return 'Palavra-passe incorreta. Tente novamente.';
|
||||
case 'email-already-in-use':
|
||||
return 'O email inserido já se encontra registrado';
|
||||
case 'operation-not-allowed':
|
||||
return 'Operação não permitida. Tente novamente.';
|
||||
case 'invalid-credential':
|
||||
return 'Credenciais inválidos. Verifique email e palavra-passe.';
|
||||
case 'too-many-requests':
|
||||
return 'Muitas tentativas. Aguarde alguns minutos antes de tentar novamente.';
|
||||
case 'network-request-failed':
|
||||
return 'Falha de conexão. Verifique sua internet e tente novamente.';
|
||||
default:
|
||||
return 'Ocorreu um problema. Tente novamente';
|
||||
}
|
||||
}
|
||||
}
|
||||
17
lib/core/services/firebase/firebase_service.dart
Normal file
17
lib/core/services/firebase/firebase_service.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
|
||||
/// Firebase Service - Central configuration and initialization
|
||||
class FirebaseService {
|
||||
/// Initialize Firebase services
|
||||
static Future<void> initialize() async {
|
||||
try {
|
||||
// Initialize Firebase Core (uses google-services.json automatically)
|
||||
await Firebase.initializeApp();
|
||||
|
||||
print('✅ Firebase initialized successfully');
|
||||
} catch (e) {
|
||||
print('❌ Firebase initialization failed: $e');
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
lib/core/theme/app_colors.dart
Normal file
68
lib/core/theme/app_colors.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// EPVC School Color Palette - New Color Scheme
|
||||
class AppColors {
|
||||
// Primary Brand Colors
|
||||
static const Color primaryTeal = Color(
|
||||
0xFF82C9BD,
|
||||
); // Main teal color - PRIMARY
|
||||
static const Color primaryOrange = Color(
|
||||
0xFFF68D2D,
|
||||
); // Accent orange - SECONDARY
|
||||
|
||||
// Gradient Colors
|
||||
static const Color gradientStart = Color(0xFF82C9BD); // Teal gradient start
|
||||
static const Color gradientEnd = Color(
|
||||
0xFF6AB8A8,
|
||||
); // Darker teal gradient end
|
||||
|
||||
// Secondary Colors
|
||||
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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// Status Colors
|
||||
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
|
||||
|
||||
// Interactive Colors
|
||||
static const Color buttonPrimary = Color(0xFF82C9BD); // Primary button (teal)
|
||||
static const Color buttonAccent = Color(0xFFF68D2D); // Accent button (orange)
|
||||
static const Color buttonSecondary = Color(0xFFE5E7EB); // Secondary button
|
||||
static const Color iconActive = Color(0xFF82C9BD); // Active icons (teal)
|
||||
static const Color iconInactive = Color(0xFF9CA3AF); // Inactive icons
|
||||
|
||||
// Chat Specific Colors
|
||||
static const Color chatBubbleStudent = Color(
|
||||
0xFF82C9BD,
|
||||
); // Student messages (teal)
|
||||
static const Color chatBubbleAI = Color(0xFFF3F4F6); // AI messages
|
||||
static const Color chatInputBackground = Color(
|
||||
0xFFF8F9FA,
|
||||
); // Input background
|
||||
static const Color chatSendButton = Color(0xFF82C9BD); // Send button (teal)
|
||||
|
||||
// Dark Mode Colors
|
||||
static const Color darkBackground = Color(0xFF1F2937); // Dark background
|
||||
static const Color darkSurface = Color(0xFF374151); // Dark surface
|
||||
static const Color darkTextPrimary = Color(0xFFF9FAFB); // Dark primary text
|
||||
static const Color darkTextSecondary = Color(
|
||||
0xFFD1D5DB,
|
||||
); // Dark secondary text
|
||||
|
||||
// Legacy compatibility (for existing code)
|
||||
@deprecated
|
||||
static const Color primaryBlue = primaryTeal; // Map old primaryBlue to new primaryTeal
|
||||
}
|
||||
412
lib/core/theme/app_theme.dart
Normal file
412
lib/core/theme/app_theme.dart
Normal file
@@ -0,0 +1,412 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'app_colors.dart';
|
||||
|
||||
/// Application Theme Configuration
|
||||
class AppTheme {
|
||||
static ThemeData get lightTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: AppColors.primaryBlue,
|
||||
brightness: Brightness.light,
|
||||
primary: AppColors.primaryBlue,
|
||||
secondary: AppColors.primaryTeal,
|
||||
surface: AppColors.surface,
|
||||
background: AppColors.background,
|
||||
error: AppColors.error,
|
||||
),
|
||||
|
||||
// App Bar Theme
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: AppColors.surface,
|
||||
foregroundColor: AppColors.textPrimary,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
systemOverlayStyle: SystemUiOverlayStyle.dark,
|
||||
titleTextStyle: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
|
||||
// Card Theme
|
||||
cardTheme: CardThemeData(
|
||||
color: AppColors.cardBackground,
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withOpacity(0.08),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
|
||||
// Elevated Button Theme
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.buttonPrimary,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 2,
|
||||
shadowColor: AppColors.primaryBlue.withOpacity(0.3),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
|
||||
// Outlined Button Theme
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: AppColors.primaryBlue,
|
||||
side: BorderSide(color: AppColors.primaryBlue.withOpacity(0.3)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
|
||||
// Text Button Theme
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColors.primaryBlue,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
textStyle: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
|
||||
// Input Field Theme
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: AppColors.surface,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: AppColors.primaryBlue.withOpacity(0.3)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: AppColors.primaryBlue.withOpacity(0.3)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: AppColors.primaryBlue, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: AppColors.error),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
hintStyle: const TextStyle(color: AppColors.textHint, fontSize: 14),
|
||||
labelStyle: const TextStyle(
|
||||
color: AppColors.textSecondary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
// Text Field Theme
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: AppColors.primaryBlue,
|
||||
selectionColor: AppColors.primaryBlue.withOpacity(0.3),
|
||||
selectionHandleColor: AppColors.primaryBlue,
|
||||
),
|
||||
|
||||
// Text Theme
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
headlineLarge: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleLarge: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
color: AppColors.textSecondary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
labelLarge: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelMedium: TextStyle(
|
||||
color: AppColors.textSecondary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
color: AppColors.textHint,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
// Bottom Navigation Bar Theme
|
||||
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
|
||||
backgroundColor: AppColors.surface,
|
||||
selectedItemColor: AppColors.primaryBlue,
|
||||
unselectedItemColor: AppColors.iconInactive,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: 8,
|
||||
selectedLabelStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
unselectedLabelStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
|
||||
// Floating Action Button Theme
|
||||
floatingActionButtonTheme: FloatingActionButtonThemeData(
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
),
|
||||
|
||||
// Divider Theme
|
||||
dividerTheme: const DividerThemeData(
|
||||
color: AppColors.buttonSecondary,
|
||||
thickness: 1,
|
||||
space: 1,
|
||||
),
|
||||
|
||||
// Icon Theme
|
||||
iconTheme: const IconThemeData(color: AppColors.iconActive, size: 24),
|
||||
|
||||
// Progress Indicator Theme
|
||||
progressIndicatorTheme: const ProgressIndicatorThemeData(
|
||||
color: AppColors.primaryBlue,
|
||||
linearTrackColor: AppColors.buttonSecondary,
|
||||
circularTrackColor: AppColors.buttonSecondary,
|
||||
),
|
||||
|
||||
// Chip Theme
|
||||
chipTheme: ChipThemeData(
|
||||
backgroundColor: AppColors.buttonSecondary,
|
||||
selectedColor: AppColors.primaryBlue.withOpacity(0.1),
|
||||
disabledColor: AppColors.buttonSecondary.withOpacity(0.5),
|
||||
labelStyle: const TextStyle(color: AppColors.textPrimary),
|
||||
secondaryLabelStyle: const TextStyle(color: AppColors.textPrimary),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData get darkTheme {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.dark,
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: AppColors.primaryBlue,
|
||||
brightness: Brightness.dark,
|
||||
primary: AppColors.primaryBlue,
|
||||
secondary: AppColors.primaryTeal,
|
||||
surface: AppColors.darkSurface,
|
||||
background: AppColors.darkBackground,
|
||||
error: AppColors.error,
|
||||
),
|
||||
|
||||
// Dark mode specific overrides would go here
|
||||
appBarTheme: const AppBarTheme(
|
||||
backgroundColor: AppColors.darkSurface,
|
||||
foregroundColor: AppColors.darkTextPrimary,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
systemOverlayStyle: SystemUiOverlayStyle.light,
|
||||
titleTextStyle: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
|
||||
cardTheme: CardThemeData(
|
||||
color: AppColors.darkSurface,
|
||||
elevation: 2,
|
||||
shadowColor: Colors.black.withOpacity(0.3),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
|
||||
// Input Field Theme for Dark Mode
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: AppColors.darkSurface,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: AppColors.primaryBlue.withOpacity(0.3)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: AppColors.primaryBlue.withOpacity(0.3)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: AppColors.primaryBlue, width: 2),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: AppColors.error),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
hintStyle: const TextStyle(
|
||||
color: AppColors.darkTextSecondary,
|
||||
fontSize: 14,
|
||||
),
|
||||
labelStyle: const TextStyle(
|
||||
color: AppColors.darkTextSecondary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
// Text Field Theme for Dark Mode
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: AppColors.primaryBlue,
|
||||
selectionColor: AppColors.primaryBlue.withOpacity(0.3),
|
||||
selectionHandleColor: AppColors.primaryBlue,
|
||||
),
|
||||
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
displaySmall: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
headlineLarge: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
headlineMedium: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
headlineSmall: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleLarge: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleMedium: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleSmall: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
bodyLarge: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
bodyMedium: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
bodySmall: TextStyle(
|
||||
color: AppColors.darkTextSecondary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
labelLarge: TextStyle(
|
||||
color: AppColors.darkTextPrimary,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelMedium: TextStyle(
|
||||
color: AppColors.darkTextSecondary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
labelSmall: TextStyle(
|
||||
color: AppColors.darkTextSecondary,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user