1683 lines
52 KiB
Markdown
1683 lines
52 KiB
Markdown
# Frontend MVP Tasks - AI Study Assistant
|
|
|
|
## 📱 MVP FRONTEND ROADMAP (8-12 WEEKS)
|
|
|
|
---
|
|
|
|
## 🎯 WEEK 1-2: FOUNDATION & SETUP
|
|
|
|
### Task 1.1: Flutter Project Initialization
|
|
**Priority**: Critical
|
|
**Estimated Time**: 4 hours
|
|
**Dependencies**: None
|
|
|
|
#### Subtasks:
|
|
- [ ] Create new Flutter project: `flutter create teachit`
|
|
- [ ] Configure Flutter SDK version (3.41.9 or latest stable)
|
|
- [ ] Set up version control (Git repository)
|
|
- [ ] Create initial project structure
|
|
- [ ] Configure pubspec.yaml with initial dependencies
|
|
|
|
#### Dependencies to Add:
|
|
```yaml
|
|
dependencies:
|
|
flutter:
|
|
sdk: flutter
|
|
|
|
# State Management
|
|
flutter_riverpod: ^2.4.9
|
|
|
|
# Firebase
|
|
firebase_core: ^2.24.2
|
|
firebase_auth: ^4.15.3
|
|
cloud_firestore: ^4.13.6
|
|
firebase_storage: ^11.5.6
|
|
|
|
# UI Components
|
|
cupertino_icons: ^1.0.6
|
|
google_fonts: ^6.1.0
|
|
|
|
# Navigation
|
|
go_router: ^12.1.3
|
|
|
|
# HTTP & Networking
|
|
http: ^1.1.2
|
|
dio: ^5.4.0
|
|
|
|
# Local Storage
|
|
shared_preferences: ^2.2.2
|
|
|
|
# Utilities
|
|
intl: ^0.19.0
|
|
uuid: ^4.2.1
|
|
|
|
dev_dependencies:
|
|
flutter_test:
|
|
sdk: flutter
|
|
flutter_lints: ^3.0.1
|
|
mockito: ^5.4.4
|
|
build_runner: ^2.4.7
|
|
```
|
|
|
|
#### Implementation Details:
|
|
```bash
|
|
# Create project
|
|
flutter create teachit
|
|
cd teachit
|
|
|
|
# Initialize git
|
|
git init
|
|
git add .
|
|
git commit -m "Initial Flutter project setup"
|
|
|
|
# Add dependencies
|
|
flutter pub add flutter_riverpod firebase_core firebase_auth cloud_firestore firebase_storage cupertino_icons google_fonts go_router http dio shared_preferences intl uuid
|
|
flutter pub add --dev flutter_test flutter_lints mockito build_runner
|
|
```
|
|
|
|
---
|
|
|
|
### Task 1.2: Project Structure Setup
|
|
**Priority**: Critical
|
|
**Estimated Time**: 6 hours
|
|
**Dependencies**: Task 1.1
|
|
|
|
#### Folder Structure to Create:
|
|
```
|
|
lib/
|
|
├── main.dart
|
|
├── app/
|
|
│ ├── app.dart
|
|
│ ├── router/
|
|
│ │ ├── app_router.dart
|
|
│ │ └── routes.dart
|
|
│ └── theme/
|
|
│ ├── app_theme.dart
|
|
│ ├── app_colors.dart
|
|
│ ├── app_text_styles.dart
|
|
│ └── app_spacing.dart
|
|
├── core/
|
|
│ ├── constants/
|
|
│ │ ├── app_constants.dart
|
|
│ │ └── firebase_constants.dart
|
|
│ ├── utils/
|
|
│ │ ├── logger.dart
|
|
│ │ ├── validators.dart
|
|
│ │ └── extensions.dart
|
|
│ ├── errors/
|
|
│ │ ├── exceptions.dart
|
|
│ │ └── failures.dart
|
|
│ └── services/
|
|
│ ├── storage_service.dart
|
|
│ └── notification_service.dart
|
|
├── features/
|
|
│ ├── auth/
|
|
│ │ ├── data/
|
|
│ │ │ ├── datasources/
|
|
│ │ │ │ ├── auth_remote_datasource.dart
|
|
│ │ │ │ └── auth_local_datasource.dart
|
|
│ │ │ ├── models/
|
|
│ │ │ │ ├── user_model.dart
|
|
│ │ │ │ └── auth_result_model.dart
|
|
│ │ │ └── repositories/
|
|
│ │ │ └── auth_repository_impl.dart
|
|
│ │ ├── domain/
|
|
│ │ │ ├── entities/
|
|
│ │ │ │ ├── user.dart
|
|
│ │ │ │ └── auth_result.dart
|
|
│ │ │ ├── repositories/
|
|
│ │ │ │ └── auth_repository.dart
|
|
│ │ │ └── usecases/
|
|
│ │ │ ├── sign_in.dart
|
|
│ │ │ ├── sign_up.dart
|
|
│ │ │ ├── sign_out.dart
|
|
│ │ │ └── get_current_user.dart
|
|
│ │ └── presentation/
|
|
│ │ ├── providers/
|
|
│ │ │ ├── auth_provider.dart
|
|
│ │ │ └── user_provider.dart
|
|
│ │ ├── screens/
|
|
│ │ │ ├── login_screen.dart
|
|
│ │ │ ├── signup_screen.dart
|
|
│ │ │ └── forgot_password_screen.dart
|
|
│ │ └── widgets/
|
|
│ │ ├── auth_form.dart
|
|
│ │ ├── social_login_button.dart
|
|
│ │ └── password_input_field.dart
|
|
│ ├── student/
|
|
│ │ ├── data/
|
|
│ │ │ ├── datasources/
|
|
│ │ │ │ ├── student_remote_datasource.dart
|
|
│ │ │ │ └── student_local_datasource.dart
|
|
│ │ │ ├── models/
|
|
│ │ │ │ ├── student_model.dart
|
|
│ │ │ │ ├── learning_state_model.dart
|
|
│ │ │ │ └── quiz_attempt_model.dart
|
|
│ │ │ └── repositories/
|
|
│ │ │ └── student_repository_impl.dart
|
|
│ │ ├── domain/
|
|
│ │ │ ├── entities/
|
|
│ │ │ │ ├── student.dart
|
|
│ │ │ │ ├── learning_state.dart
|
|
│ │ │ │ ├── concept_mastery.dart
|
|
│ │ │ │ └── quiz_attempt.dart
|
|
│ │ │ ├── repositories/
|
|
│ │ │ │ └── student_repository.dart
|
|
│ │ │ └── usecases/
|
|
│ │ │ ├── get_learning_state.dart
|
|
│ │ │ ├── update_learning_state.dart
|
|
│ │ │ ├── submit_quiz_attempt.dart
|
|
│ │ │ └── get_quiz_history.dart
|
|
│ │ └── presentation/
|
|
│ │ ├── providers/
|
|
│ │ │ ├── student_provider.dart
|
|
│ │ │ ├── learning_state_provider.dart
|
|
│ │ │ └── quiz_provider.dart
|
|
│ │ ├── screens/
|
|
│ │ │ ├── student_dashboard_screen.dart
|
|
│ │ │ ├── ask_tutor_screen.dart
|
|
│ │ │ ├── quiz_screen.dart
|
|
│ │ │ ├── progress_screen.dart
|
|
│ │ │ └── profile_screen.dart
|
|
│ │ └── widgets/
|
|
│ │ ├── mastery_progress_bar.dart
|
|
│ │ ├── concept_card.dart
|
|
│ │ ├── quiz_question_card.dart
|
|
│ │ ├── chat_bubble.dart
|
|
│ │ └── feedback_widget.dart
|
|
│ ├── teacher/
|
|
│ │ ├── data/
|
|
│ │ │ ├── datasources/
|
|
│ │ │ │ ├── teacher_remote_datasource.dart
|
|
│ │ │ │ └── teacher_local_datasource.dart
|
|
│ │ │ ├── models/
|
|
│ │ │ │ ├── teacher_model.dart
|
|
│ │ │ │ ├── content_model.dart
|
|
│ │ │ │ └── quiz_model.dart
|
|
│ │ │ └── repositories/
|
|
│ │ │ └── teacher_repository_impl.dart
|
|
│ │ ├── domain/
|
|
│ │ │ ├── entities/
|
|
│ │ │ │ ├── teacher.dart
|
|
│ │ │ │ ├── content.dart
|
|
│ │ │ │ └── quiz.dart
|
|
│ │ │ ├── repositories/
|
|
│ │ │ │ └── teacher_repository.dart
|
|
│ │ │ └── usecases/
|
|
│ │ │ ├── upload_content.dart
|
|
│ │ │ ├── create_quiz.dart
|
|
│ │ │ ├── get_class_analytics.dart
|
|
│ │ │ └── manage_students.dart
|
|
│ │ └── presentation/
|
|
│ │ ├── providers/
|
|
│ │ │ ├── teacher_provider.dart
|
|
│ │ │ ├── content_provider.dart
|
|
│ │ │ └── analytics_provider.dart
|
|
│ │ ├── screens/
|
|
│ │ │ ├── teacher_dashboard_screen.dart
|
|
│ │ │ ├── upload_content_screen.dart
|
|
│ │ │ ├── create_quiz_screen.dart
|
|
│ │ │ ├── class_analytics_screen.dart
|
|
│ │ │ └── manage_students_screen.dart
|
|
│ │ └── widgets/
|
|
│ │ ├── content_upload_card.dart
|
|
│ │ ├── quiz_builder.dart
|
|
│ │ ├── analytics_chart.dart
|
|
│ │ └── student_list_item.dart
|
|
│ └── shared/
|
|
│ ├── data/
|
|
│ │ ├── models/
|
|
│ │ │ ├── message_model.dart
|
|
│ │ │ ├── feedback_model.dart
|
|
│ │ │ └── notification_model.dart
|
|
│ │ └── datasources/
|
|
│ │ ├── shared_remote_datasource.dart
|
|
│ │ └── shared_local_datasource.dart
|
|
│ ├── domain/
|
|
│ │ ├── entities/
|
|
│ │ │ ├── message.dart
|
|
│ │ │ ├── feedback.dart
|
|
│ │ │ └── notification.dart
|
|
│ │ └── repositories/
|
|
│ │ └── shared_repository.dart
|
|
│ └── presentation/
|
|
│ ├── widgets/
|
|
│ │ ├── custom_button.dart
|
|
│ │ ├── custom_text_field.dart
|
|
│ │ ├── loading_widget.dart
|
|
│ │ ├── error_widget.dart
|
|
│ │ ├── empty_state_widget.dart
|
|
│ │ ├── confirmation_dialog.dart
|
|
│ │ └── bottom_sheet.dart
|
|
│ └── providers/
|
|
│ ├── theme_provider.dart
|
|
│ ├── connectivity_provider.dart
|
|
│ └── notification_provider.dart
|
|
└── widgets/
|
|
├── app_scaffold.dart
|
|
├── app_bottom_navigation.dart
|
|
└── app_drawer.dart
|
|
```
|
|
|
|
---
|
|
|
|
### Task 1.3: Theme & Design System Implementation
|
|
**Priority**: Critical
|
|
**Estimated Time**: 8 hours
|
|
**Dependencies**: Task 1.2
|
|
|
|
#### Subtasks:
|
|
- [ ] Implement AppColors class with EPVChat color palette
|
|
- [ ] Create AppTextStyles with typography system
|
|
- [ ] Set up AppTheme with light/dark mode support
|
|
- [ ] Implement custom widgets (buttons, cards, inputs)
|
|
- [ ] Create animation utilities
|
|
- [ ] Set up responsive design utilities
|
|
|
|
#### Implementation Files:
|
|
|
|
**lib/app/theme/app_colors.dart**
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
|
|
class AppColors {
|
|
// Primary Brand Colors (from EPVChat)
|
|
static const Color primaryBlue = Color(0xFF4A90E2);
|
|
static const Color primaryTeal = Color(0xFF5AC8FA);
|
|
static const Color primaryOrange = Color(0xFFFF9500);
|
|
|
|
// Gradient Colors
|
|
static const Color gradientStart = Color(0xFF4A90E2);
|
|
static const Color gradientEnd = Color(0xFF5AC8FA);
|
|
|
|
// Neutral Colors
|
|
static const Color background = Color(0xFFF8F9FA);
|
|
static const Color surface = Color(0xFFFFFFFF);
|
|
static const Color cardBackground = Color(0xFFFFFFFF);
|
|
|
|
// Text Colors
|
|
static const Color textPrimary = Color(0xFF1A1A1A);
|
|
static const Color textSecondary = Color(0xFF6B7280);
|
|
static const Color textHint = Color(0xFF9CA3AF);
|
|
|
|
// Status Colors
|
|
static const Color success = Color(0xFF10B981);
|
|
static const Color warning = Color(0xFFF59E0B);
|
|
static const Color error = Color(0xFFEF4444);
|
|
static const Color info = Color(0xFF3B82F6);
|
|
|
|
// Interactive Colors
|
|
static const Color buttonPrimary = Color(0xFF4A90E2);
|
|
static const Color buttonSecondary = Color(0xFFE5E7EB);
|
|
static const Color iconActive = Color(0xFF4A90E2);
|
|
static const Color iconInactive = Color(0xFF9CA3AF);
|
|
|
|
// Chat Colors
|
|
static const Color chatBubbleStudent = Color(0xFF4A90E2);
|
|
static const Color chatBubbleAI = Color(0xFFF3F4F6);
|
|
static const Color chatInputBackground = Color(0xFFF8F9FA);
|
|
static const Color chatSendButton = Color(0xFF5AC8FA);
|
|
}
|
|
|
|
class AppDarkColors {
|
|
static const Color background = Color(0xFF121212);
|
|
static const Color surface = Color(0xFF1E1E1E);
|
|
static const Color cardBackground = Color(0xFF2D2D2D);
|
|
static const Color textPrimary = Color(0xFFFFFFFF);
|
|
static const Color textSecondary = Color(0xFFB3B3B3);
|
|
static const Color textHint = Color(0xFF808080);
|
|
}
|
|
```
|
|
|
|
**lib/app/theme/app_theme.dart**
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
import 'app_colors.dart';
|
|
import 'app_text_styles.dart';
|
|
|
|
class AppTheme {
|
|
static ThemeData get lightTheme {
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
brightness: Brightness.light,
|
|
colorScheme: const ColorScheme.light(
|
|
primary: AppColors.primaryBlue,
|
|
secondary: AppColors.primaryTeal,
|
|
surface: AppColors.surface,
|
|
background: AppColors.background,
|
|
error: AppColors.error,
|
|
onPrimary: Colors.white,
|
|
onSecondary: Colors.white,
|
|
onSurface: AppColors.textPrimary,
|
|
onBackground: AppColors.textPrimary,
|
|
onError: Colors.white,
|
|
),
|
|
textTheme: const TextTheme(
|
|
displayLarge: AppTextStyles.h1,
|
|
displayMedium: AppTextStyles.h2,
|
|
displaySmall: AppTextStyles.h3,
|
|
bodyLarge: AppTextStyles.bodyLarge,
|
|
bodyMedium: AppTextStyles.bodyMedium,
|
|
bodySmall: AppTextStyles.bodySmall,
|
|
labelLarge: AppTextStyles.buttonLarge,
|
|
labelMedium: AppTextStyles.buttonMedium,
|
|
labelSmall: AppTextStyles.caption,
|
|
),
|
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.buttonPrimary,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
|
),
|
|
),
|
|
inputDecorationTheme: InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: AppColors.surface,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(
|
|
color: AppColors.primaryBlue.withOpacity(0.3),
|
|
width: 1,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide(
|
|
color: AppColors.primaryBlue.withOpacity(0.3),
|
|
width: 1,
|
|
),
|
|
),
|
|
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,
|
|
width: 1,
|
|
),
|
|
),
|
|
),
|
|
cardTheme: CardTheme(
|
|
color: AppColors.cardBackground,
|
|
elevation: 4,
|
|
shadowColor: Colors.black.withOpacity(0.1),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
static ThemeData get darkTheme {
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
brightness: Brightness.dark,
|
|
colorScheme: const ColorScheme.dark(
|
|
primary: AppColors.primaryBlue,
|
|
secondary: AppColors.primaryTeal,
|
|
surface: AppDarkColors.surface,
|
|
background: AppDarkColors.background,
|
|
error: AppColors.error,
|
|
onPrimary: Colors.white,
|
|
onSecondary: Colors.white,
|
|
onSurface: AppDarkColors.textPrimary,
|
|
onBackground: AppDarkColors.textPrimary,
|
|
onError: Colors.white,
|
|
),
|
|
// ... similar text theme and component themes for dark mode
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Task 1.4: Firebase Configuration
|
|
**Priority**: Critical
|
|
**Estimated Time**: 4 hours
|
|
**Dependencies**: Task 1.1
|
|
|
|
#### Subtasks:
|
|
- [ ] Create Firebase project
|
|
- [ ] Add Firebase configuration files
|
|
- [ ] Initialize Firebase in app
|
|
- [ ] Set up Firebase Auth configuration
|
|
- [ ] Configure Firestore security rules
|
|
- [ ] Set up Firebase Storage
|
|
|
|
#### Implementation:
|
|
|
|
**lib/core/constants/firebase_constants.dart**
|
|
```dart
|
|
class FirebaseConstants {
|
|
// Collections
|
|
static const String usersCollection = 'users';
|
|
static const String schoolsCollection = 'schools';
|
|
static const String learningStatesCollection = 'learning_states';
|
|
static const String contentChunksCollection = 'content_chunks';
|
|
static const String quizzesCollection = 'quizzes';
|
|
static const String quizAttemptsCollection = 'quiz_attempts';
|
|
static const String interactionsCollection = 'interactions';
|
|
static const String auditLogsCollection = 'audit_logs';
|
|
|
|
// Storage paths
|
|
static const String contentStoragePath = 'content/';
|
|
static const String profileImagesPath = 'profile_images/';
|
|
static const String tempFilesPath = 'temp/';
|
|
|
|
// User roles
|
|
static const String studentRole = 'student';
|
|
static const String teacherRole = 'teacher';
|
|
static const String adminRole = 'admin';
|
|
|
|
// Default values
|
|
static const int defaultQuizDuration = 30; // minutes
|
|
static const int maxChunkSize = 400; // tokens
|
|
static const int maxRetrievalChunks = 5;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🔐 WEEK 3-4: AUTHENTICATION & USER MANAGEMENT
|
|
|
|
### Task 2.1: Authentication System
|
|
**Priority**: Critical
|
|
**Estimated Time**: 12 hours
|
|
**Dependencies**: Task 1.4
|
|
|
|
#### Subtasks:
|
|
- [ ] Implement Firebase Auth service
|
|
- [ ] Create user models and entities
|
|
- [ ] Build authentication repository
|
|
- [ ] Implement sign in use case
|
|
- [ ] Implement sign up use case
|
|
- [ ] Implement password reset
|
|
- [ ] Create authentication providers
|
|
- [ ] Build login/signup screens
|
|
|
|
#### Implementation:
|
|
|
|
**lib/features/auth/data/datasources/auth_remote_datasource.dart**
|
|
```dart
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import '../models/user_model.dart';
|
|
|
|
abstract class AuthRemoteDataSource {
|
|
Future<UserModel> signInWithEmail(String email, String password);
|
|
Future<UserModel> signUpWithEmail(String email, String password, String name, String role);
|
|
Future<UserModel> signInWithGoogle();
|
|
Future<void> sendPasswordResetEmail(String email);
|
|
Future<void> signOut();
|
|
Future<UserModel?> getCurrentUser();
|
|
Stream<UserModel?> authStateChanges();
|
|
}
|
|
|
|
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
|
final FirebaseAuth _firebaseAuth;
|
|
|
|
AuthRemoteDataSourceImpl(this._firebaseAuth);
|
|
|
|
@override
|
|
Future<UserModel> signInWithEmail(String email, String password) async {
|
|
try {
|
|
final result = await _firebaseAuth.signInWithEmailAndPassword(
|
|
email: email,
|
|
password: password,
|
|
);
|
|
return UserModel.fromFirebaseUser(result.user!);
|
|
} catch (e) {
|
|
throw AuthException(e.toString());
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<UserModel> signUpWithEmail(
|
|
String email,
|
|
String password,
|
|
String name,
|
|
String role
|
|
) async {
|
|
try {
|
|
final result = await _firebaseAuth.createUserWithEmailAndPassword(
|
|
email: email,
|
|
password: password,
|
|
);
|
|
|
|
// Update user profile
|
|
await result.user!.updateDisplayName(name);
|
|
|
|
return UserModel.fromFirebaseUser(
|
|
result.user!,
|
|
role: role,
|
|
displayName: name,
|
|
);
|
|
} catch (e) {
|
|
throw AuthException(e.toString());
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<UserModel> signInWithGoogle() async {
|
|
// Implement Google Sign-In
|
|
throw UnimplementedError();
|
|
}
|
|
|
|
@override
|
|
Future<void> sendPasswordResetEmail(String email) async {
|
|
try {
|
|
await _firebaseAuth.sendPasswordResetEmail(email: email);
|
|
} catch (e) {
|
|
throw AuthException(e.toString());
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> signOut() async {
|
|
await _firebaseAuth.signOut();
|
|
}
|
|
|
|
@override
|
|
Future<UserModel?> getCurrentUser() async {
|
|
final user = _firebaseAuth.currentUser;
|
|
return user != null ? UserModel.fromFirebaseUser(user) : null;
|
|
}
|
|
|
|
@override
|
|
Stream<UserModel?> authStateChanges() {
|
|
return _firebaseAuth.authStateChanges().map((user) {
|
|
return user != null ? UserModel.fromFirebaseUser(user) : null;
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**lib/features/auth/presentation/screens/login_screen.dart**
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../providers/auth_provider.dart';
|
|
import '../../../shared/widgets/custom_button.dart';
|
|
import '../../../shared/widgets/custom_text_field.dart';
|
|
|
|
class LoginScreen extends ConsumerStatefulWidget {
|
|
const LoginScreen({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
ConsumerState<LoginScreen> createState() => _LoginScreenState();
|
|
}
|
|
|
|
class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|
final _emailController = TextEditingController();
|
|
final _passwordController = TextEditingController();
|
|
final _formKey = GlobalKey<FormState>();
|
|
bool _obscurePassword = true;
|
|
bool _isLoading = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_emailController.dispose();
|
|
_passwordController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _handleLogin() async {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
|
|
setState(() => _isLoading = true);
|
|
|
|
try {
|
|
await ref.read(authProvider.notifier).signIn(
|
|
email: _emailController.text.trim(),
|
|
password: _passwordController.text,
|
|
);
|
|
} catch (e) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Login failed: ${e.toString()}'),
|
|
backgroundColor: Theme.of(context).colorScheme.error,
|
|
),
|
|
);
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Container(
|
|
decoration: const BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [
|
|
AppColors.gradientStart,
|
|
AppColors.gradientEnd,
|
|
],
|
|
),
|
|
),
|
|
child: SafeArea(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const SizedBox(height: 60),
|
|
|
|
// Logo and Title
|
|
Column(
|
|
children: [
|
|
Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(20),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: const Icon(
|
|
Icons.school,
|
|
size: 40,
|
|
color: AppColors.primaryBlue,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'AI Study Assistant',
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text(
|
|
'Escola Profissional de Vila do Conde',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.white70,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 60),
|
|
|
|
// Login Form
|
|
Container(
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const Text(
|
|
'Welcome Back',
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
CustomTextField(
|
|
controller: _emailController,
|
|
label: 'Email',
|
|
hint: 'Enter your email',
|
|
keyboardType: TextInputType.emailAddress,
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Please enter your email';
|
|
}
|
|
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
|
|
return 'Please enter a valid email';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
CustomTextField(
|
|
controller: _passwordController,
|
|
label: 'Password',
|
|
hint: 'Enter your password',
|
|
obscureText: _obscurePassword,
|
|
suffixIcon: IconButton(
|
|
icon: Icon(
|
|
_obscurePassword ? Icons.visibility : Icons.visibility_off,
|
|
),
|
|
onPressed: () {
|
|
setState(() => _obscurePassword = !_obscurePassword);
|
|
},
|
|
),
|
|
validator: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return 'Please enter your password';
|
|
}
|
|
if (value.length < 6) {
|
|
return 'Password must be at least 6 characters';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
CustomButton(
|
|
text: 'Sign In',
|
|
onPressed: _handleLogin,
|
|
isLoading: _isLoading,
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
TextButton(
|
|
onPressed: () {
|
|
// Navigate to forgot password
|
|
},
|
|
child: const Text(
|
|
'Forgot Password?',
|
|
style: TextStyle(
|
|
color: AppColors.primaryBlue,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
// Sign Up Link
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
"Don't have an account? ",
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
// Navigate to sign up
|
|
},
|
|
child: const Text(
|
|
'Sign Up',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2.2: User Role Management
|
|
**Priority**: High
|
|
**Estimated Time**: 8 hours
|
|
**Dependencies**: Task 2.1
|
|
|
|
#### Subtasks:
|
|
- [ ] Implement role-based routing
|
|
- [ ] Create user profile screens
|
|
- [ ] Build role-specific navigation
|
|
- [ ] Implement user settings
|
|
- [ ] Create user management (for teachers/admins)
|
|
|
|
---
|
|
|
|
## 📚 WEEK 5-6: STUDENT FEATURES
|
|
|
|
### Task 3.1: Student Dashboard
|
|
**Priority**: High
|
|
**Estimated Time**: 16 hours
|
|
**Dependencies**: Task 2.2
|
|
|
|
#### Subtasks:
|
|
- [ ] Design dashboard layout
|
|
- [ ] Implement mastery progress cards
|
|
- [ ] Create concept review widgets
|
|
- [ ] Build misconception indicators
|
|
- [ ] Add learning streak tracker
|
|
- [ ] Implement quick action buttons
|
|
|
|
#### Implementation:
|
|
|
|
**lib/features/student/presentation/screens/student_dashboard_screen.dart**
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../providers/learning_state_provider.dart';
|
|
import '../../../shared/widgets/mastery_progress_bar.dart';
|
|
import '../../../shared/widgets/concept_card.dart';
|
|
import '../../../shared/widgets/custom_button.dart';
|
|
|
|
class StudentDashboardScreen extends ConsumerStatefulWidget {
|
|
const StudentDashboardScreen({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
ConsumerState<StudentDashboardScreen> createState() => _StudentDashboardScreenState();
|
|
}
|
|
|
|
class _StudentDashboardScreenState extends ConsumerState<StudentDashboardScreen>
|
|
with TickerProviderStateMixin {
|
|
late AnimationController _fadeController;
|
|
late AnimationController _slideController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_fadeController = AnimationController(
|
|
duration: const Duration(milliseconds: 600),
|
|
vsync: this,
|
|
);
|
|
_slideController = AnimationController(
|
|
duration: const Duration(milliseconds: 800),
|
|
vsync: this,
|
|
);
|
|
|
|
_fadeController.forward();
|
|
_slideController.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_fadeController.dispose();
|
|
_slideController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final learningState = ref.watch(learningStateProvider);
|
|
final recommendations = ref.watch(recommendationsProvider);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('My Learning'),
|
|
backgroundColor: AppColors.primaryBlue,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
),
|
|
body: RefreshIndicator(
|
|
onRefresh: () async {
|
|
await ref.refresh(learningStateProvider.future);
|
|
},
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Welcome Section
|
|
FadeTransition(
|
|
opacity: _fadeController,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Welcome back, ${learningState.value?.profile.name ?? 'Student'}!',
|
|
style: AppTextStyles.h2,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Continue your learning journey',
|
|
style: AppTextStyles.bodyMedium.copyWith(
|
|
color: AppColors.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Summary Cards
|
|
SlideTransition(
|
|
position: Tween<Offset>(
|
|
begin: const Offset(0, 0.3),
|
|
end: Offset.zero,
|
|
).animate(CurvedAnimation(
|
|
parent: _slideController,
|
|
curve: Curves.easeOut,
|
|
)),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildSummaryCard(
|
|
title: 'Average Mastery',
|
|
value: '${(learningState.value?.averageMastery * 100 ?? 0).toInt()}%',
|
|
icon: Icons.trending_up,
|
|
color: AppColors.success,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildSummaryCard(
|
|
title: 'Learning Streak',
|
|
value: '${learningState.value?.learningStreak ?? 0} days',
|
|
icon: Icons.local_fire_department,
|
|
color: AppColors.primaryOrange,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Concepts to Review
|
|
_buildSectionTitle('Concepts to Review'),
|
|
const SizedBox(height: 12),
|
|
SizedBox(
|
|
height: 120,
|
|
child: ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: learningState.value?.spacedRepetition.length ?? 0,
|
|
itemBuilder: (context, index) {
|
|
final concept = learningState.value!.spacedRepetition[index];
|
|
return Container(
|
|
width: 200,
|
|
margin: const EdgeInsets.only(right: 12),
|
|
child: ConceptCard(
|
|
conceptId: concept.conceptId,
|
|
dueDate: concept.dueDate,
|
|
priority: concept.priority,
|
|
onTap: () {
|
|
// Navigate to concept details
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Misconceptions
|
|
if (learningState.value?.misconceptions.isNotEmpty == true) ...[
|
|
_buildSectionTitle('Address Misconceptions'),
|
|
const SizedBox(height: 12),
|
|
...learningState.value!.misconceptions.map(
|
|
(misconception) => Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.warning.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: AppColors.warning.withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.warning,
|
|
color: AppColors.warning,
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Text(
|
|
misconception.description,
|
|
style: AppTextStyles.bodyMedium,
|
|
),
|
|
),
|
|
CustomButton(
|
|
text: 'Fix',
|
|
onPressed: () {
|
|
// Navigate to remedial content
|
|
},
|
|
isSmall: true,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
],
|
|
|
|
// Recommended Actions
|
|
_buildSectionTitle('Recommended for You'),
|
|
const SizedBox(height: 12),
|
|
...recommendations.when(
|
|
data: (actions) => actions.map(
|
|
(action) => Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.cardBackground,
|
|
borderRadius: BorderRadius.circular(12),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
_getActionIcon(action.type),
|
|
color: AppColors.primaryBlue,
|
|
size: 24,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
action.description,
|
|
style: AppTextStyles.bodyMedium,
|
|
),
|
|
if (action.estimatedTime != null) ...[
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'Est. ${action.estimatedTime} min',
|
|
style: AppTextStyles.caption,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
CustomButton(
|
|
text: 'Start',
|
|
onPressed: () => _handleAction(action),
|
|
isSmall: true,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
loading: () => const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
error: (error, stack) => Center(
|
|
child: Text('Error: $error'),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Quick Actions
|
|
_buildSectionTitle('Quick Actions'),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: CustomButton(
|
|
text: 'Ask Tutor',
|
|
onPressed: () {
|
|
// Navigate to chat
|
|
},
|
|
icon: Icons.chat,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: CustomButton(
|
|
text: 'Take Quiz',
|
|
onPressed: () {
|
|
// Navigate to quiz
|
|
},
|
|
icon: Icons.quiz,
|
|
isSecondary: true,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSummaryCard({
|
|
required String title,
|
|
required String value,
|
|
required IconData icon,
|
|
required Color color,
|
|
}) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: color.withOpacity(0.3),
|
|
),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Icon(icon, color: color, size: 24),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
title,
|
|
style: AppTextStyles.caption.copyWith(
|
|
color: color,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
value,
|
|
style: AppTextStyles.h3.copyWith(
|
|
color: color,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSectionTitle(String title) {
|
|
return Text(
|
|
title,
|
|
style: AppTextStyles.h3,
|
|
);
|
|
}
|
|
|
|
IconData _getActionIcon(String actionType) {
|
|
switch (actionType) {
|
|
case 'remedial':
|
|
return Icons.healing;
|
|
case 'review':
|
|
return Icons.refresh;
|
|
case 'new_learning':
|
|
return Icons.add_circle;
|
|
case 'exploration':
|
|
return Icons.explore;
|
|
default:
|
|
return Icons.lightbulb;
|
|
}
|
|
}
|
|
|
|
void _handleAction(RecommendedAction action) {
|
|
switch (action.type) {
|
|
case 'remedial':
|
|
// Navigate to remedial content
|
|
break;
|
|
case 'review':
|
|
// Navigate to review screen
|
|
break;
|
|
case 'new_learning':
|
|
// Navigate to new concept
|
|
break;
|
|
case 'exploration':
|
|
// Navigate to exploration
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3.2: AI Tutor Chat Interface
|
|
**Priority**: High
|
|
**Estimated Time**: 20 hours
|
|
**Dependencies**: Task 3.1
|
|
|
|
#### Subtasks:
|
|
- [ ] Design chat interface layout
|
|
- [ ] Implement message bubbles (student/AI)
|
|
- [ ] Create chat input with send button
|
|
- [ ] Add typing indicators
|
|
- [ ] Implement message history
|
|
- [ ] Add feedback collection
|
|
- [ ] Create smooth animations
|
|
|
|
#### Implementation:
|
|
|
|
**lib/features/student/presentation/screens/ask_tutor_screen.dart**
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../providers/chat_provider.dart';
|
|
import '../../../shared/widgets/chat_bubble.dart';
|
|
import '../../../shared/widgets/custom_text_field.dart';
|
|
|
|
class AskTutorScreen extends ConsumerStatefulWidget {
|
|
const AskTutorScreen({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
ConsumerState<AskTutorScreen> createState() => _AskTutorScreenState();
|
|
}
|
|
|
|
class _AskTutorScreenState extends ConsumerState<AskTutorScreen> {
|
|
final _messageController = TextEditingController();
|
|
final _scrollController = ScrollController();
|
|
final _focusNode = FocusNode();
|
|
|
|
@override
|
|
void dispose() {
|
|
_messageController.dispose();
|
|
_scrollController.dispose();
|
|
_focusNode.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _scrollToBottom() {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (_scrollController.hasClients) {
|
|
_scrollController.animateTo(
|
|
_scrollController.position.maxScrollExtent,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.easeOut,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _sendMessage() async {
|
|
final message = _messageController.text.trim();
|
|
if (message.isEmpty) return;
|
|
|
|
_messageController.clear();
|
|
_focusNode.unfocus();
|
|
|
|
try {
|
|
await ref.read(chatProvider.notifier).sendMessage(message);
|
|
_scrollToBottom();
|
|
} catch (e) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Failed to send message: $e'),
|
|
backgroundColor: Theme.of(context).colorScheme.error,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final messages = ref.watch(chatProvider);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('AI Tutor'),
|
|
backgroundColor: AppColors.primaryBlue,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
),
|
|
body: Column(
|
|
children: [
|
|
// Messages List
|
|
Expanded(
|
|
child: messages.when(
|
|
data: (messageList) => ListView.builder(
|
|
controller: _scrollController,
|
|
padding: const EdgeInsets.all(16),
|
|
itemCount: messageList.length,
|
|
itemBuilder: (context, index) {
|
|
final message = messageList[index];
|
|
return ChatBubble(
|
|
message: message,
|
|
showAvatar: message.role == 'assistant',
|
|
onFeedback: (feedback) {
|
|
ref.read(chatProvider.notifier).submitFeedback(
|
|
messageId: message.id,
|
|
feedback: feedback,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
loading: () => const Center(
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
error: (error, stack) => Center(
|
|
child: Text('Error: $error'),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Typing Indicator
|
|
Consumer(
|
|
builder: (context, ref, child) {
|
|
final isTyping = ref.watch(isTypingProvider);
|
|
return isTyping
|
|
? Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.chatBubbleAI,
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 16,
|
|
height: 16,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation(AppColors.textSecondary),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'AI is thinking...',
|
|
style: AppTextStyles.bodySmall.copyWith(
|
|
color: AppColors.textSecondary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: const SizedBox.shrink();
|
|
},
|
|
),
|
|
|
|
// Input Area
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.chatInputBackground,
|
|
border: Border(
|
|
top: BorderSide(
|
|
color: AppColors.primaryBlue.withOpacity(0.1),
|
|
),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: CustomTextField(
|
|
controller: _messageController,
|
|
hint: 'Ask a question...',
|
|
maxLines: 3,
|
|
minLines: 1,
|
|
focusNode: _focusNode,
|
|
onSubmitted: (_) => _sendMessage(),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Container(
|
|
width: 48,
|
|
height: 48,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.chatSendButton,
|
|
borderRadius: BorderRadius.circular(24),
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(24),
|
|
onTap: _sendMessage,
|
|
child: const Icon(
|
|
Icons.send,
|
|
color: Colors.white,
|
|
size: 24,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3.3: Quiz System
|
|
**Priority**: High
|
|
**Estimated Time**: 16 hours
|
|
**Dependencies**: Task 3.1
|
|
|
|
#### Subtasks:
|
|
- [ ] Create quiz question cards
|
|
- [ ] Implement multiple choice interface
|
|
- [ ] Add timer functionality
|
|
- [ ] Build progress tracking
|
|
- [ ] Create quiz results screen
|
|
- [ ] Implement quiz history
|
|
|
|
---
|
|
|
|
## 👨🏫 WEEK 7-8: TEACHER FEATURES
|
|
|
|
### Task 4.1: Teacher Dashboard
|
|
**Priority**: High
|
|
**Estimated Time**: 12 hours
|
|
**Dependencies**: Task 2.2
|
|
|
|
#### Subtasks:
|
|
- [ ] Design teacher dashboard layout
|
|
- [ ] Create class overview cards
|
|
- [ ] Implement analytics widgets
|
|
- [ ] Add quick action buttons
|
|
- [ ] Build student performance summary
|
|
|
|
---
|
|
|
|
### Task 4.2: Content Upload System
|
|
**Priority**: High
|
|
**Estimated Time**: 16 hours
|
|
**Dependencies**: Task 4.1
|
|
|
|
#### Subtasks:
|
|
- [ ] Create file upload interface
|
|
- [ ] Implement PDF parsing preview
|
|
- [ ] Add content chunking preview
|
|
- [ ] Build metadata editing
|
|
- [ ] Create content management list
|
|
|
|
---
|
|
|
|
### Task 4.3: Quiz Creation
|
|
**Priority**: Medium
|
|
**Estimated Time**: 12 hours
|
|
**Dependencies**: Task 4.1
|
|
|
|
#### Subtasks:
|
|
- [ ] Build quiz builder interface
|
|
- [ ] Add question type selection
|
|
- [ ] Implement question editor
|
|
- [ ] Create quiz settings
|
|
- [ ] Add preview functionality
|
|
|
|
---
|
|
|
|
## 🧪 WEEK 9-10: TESTING & POLISH
|
|
|
|
### Task 5.1: Unit Tests
|
|
**Priority**: High
|
|
**Estimated Time**: 16 hours
|
|
**Dependencies**: All previous tasks
|
|
|
|
#### Subtasks:
|
|
- [ ] Write tests for all use cases
|
|
- [ ] Test repository implementations
|
|
- [ ] Test provider logic
|
|
- [ ] Test widget rendering
|
|
- [ ] Test user interactions
|
|
|
|
---
|
|
|
|
### Task 5.2: Integration Tests
|
|
**Priority**: High
|
|
**Estimated Time**: 12 hours
|
|
**Dependencies**: Task 5.1
|
|
|
|
#### Subtasks:
|
|
- [ ] Test authentication flow
|
|
- [ ] Test complete user journeys
|
|
- [ ] Test Firebase integration
|
|
- [ ] Test API communication
|
|
- [ ] Test error handling
|
|
|
|
---
|
|
|
|
### Task 5.3: UI Polish
|
|
**Priority**: Medium
|
|
**Estimated Time**: 8 hours
|
|
**Dependencies**: Task 5.2
|
|
|
|
#### Subtasks:
|
|
- [ ] Refine animations
|
|
- [ ] Improve responsive design
|
|
- [ ] Add micro-interactions
|
|
- [ ] Optimize performance
|
|
- [ ] Fix UI inconsistencies
|
|
|
|
---
|
|
|
|
## 📱 WEEK 11-12: FINALIZATION
|
|
|
|
### Task 6.1: Performance Optimization
|
|
**Priority**: High
|
|
**Estimated Time**: 8 hours
|
|
**Dependencies**: Task 5.3
|
|
|
|
#### Subtasks:
|
|
- [ ] Profile app performance
|
|
- [ ] Optimize image loading
|
|
- [ ] Reduce bundle size
|
|
- [ ] Implement lazy loading
|
|
- [ ] Optimize Firebase queries
|
|
|
|
---
|
|
|
|
### Task 6.2: Documentation
|
|
**Priority**: Medium
|
|
**Estimated Time**: 8 hours
|
|
**Dependencies**: Task 6.1
|
|
|
|
#### Subtasks:
|
|
- [ ] Document API endpoints
|
|
- [ ] Create user guide
|
|
- [ ] Write deployment instructions
|
|
- [ ] Document architecture
|
|
- [ ] Create troubleshooting guide
|
|
|
|
---
|
|
|
|
## 🚀 DELIVERABLES
|
|
|
|
### Week 2 Deliverables
|
|
- [ ] Flutter project with complete structure
|
|
- [ ] Design system implementation
|
|
- [ ] Firebase configuration
|
|
- [ ] Basic authentication flow
|
|
|
|
### Week 4 Deliverables
|
|
- [ ] Complete authentication system
|
|
- [ ] User role management
|
|
- [ ] User profiles
|
|
- [ ] Navigation system
|
|
|
|
### Week 6 Deliverables
|
|
- [ ] Student dashboard
|
|
- [ ] AI tutor chat interface
|
|
- [ ] Quiz system
|
|
- [ ] Progress tracking
|
|
|
|
### Week 8 Deliverables
|
|
- [ ] Teacher dashboard
|
|
- [ ] Content upload system
|
|
- [ ] Quiz creation tools
|
|
- [ ] Basic analytics
|
|
|
|
### Week 10 Deliverables
|
|
- [ ] Complete test suite
|
|
- [ ] Polished UI
|
|
- [ ] Error handling
|
|
- [ ] Performance optimizations
|
|
|
|
### Week 12 Deliverables
|
|
- [ ] Production-ready app
|
|
- [ ] Complete documentation
|
|
- [ ] Deployment ready
|
|
- [ ] User testing feedback incorporated
|
|
|
|
---
|
|
|
|
## 📋 QUALITY CHECKLIST
|
|
|
|
### Code Quality
|
|
- [ ] All code follows Flutter/Dart conventions
|
|
- [ ] Proper error handling throughout
|
|
- [ ] No hardcoded values (use constants)
|
|
- [ ] Proper state management implementation
|
|
- [ ] Clean architecture maintained
|
|
|
|
### UI/UX Quality
|
|
- [ ] Design system consistently applied
|
|
- [ ] Responsive design works on all screen sizes
|
|
- [ ] Animations are smooth and purposeful
|
|
- [ ] Accessibility features implemented
|
|
- [ ] Dark mode support
|
|
|
|
### Performance
|
|
- [ ] App launches within 3 seconds
|
|
- [ ] Smooth 60 FPS animations
|
|
- [ ] Memory usage optimized
|
|
- [ ] Network requests efficient
|
|
- [ ] No memory leaks
|
|
|
|
### Testing
|
|
- [ ] Unit test coverage > 80%
|
|
- [ ] All critical paths tested
|
|
- [ ] Firebase integration tested
|
|
- [ ] Error scenarios tested
|
|
- [ ] Performance benchmarks met
|
|
|
|
---
|
|
|
|
*Last Updated: 2026-05-06*
|
|
*Version: 1.0.0*
|
|
*Frontend Lead: Flutter Development Team*
|