Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions LearningLens2025/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ terraform/terraformout.txt
# npm

/lambda/code_eval/node_modules
/lambda/gettoken/node_modules
/lambda/ai_log/node_modules
/lambda/game_data/node_modules
/lambda/gettoken/gettoken.zip
/lambda/reflections/node_modules
/lambda/ai_log/ai_log.zip
/lambda/code_eval/code_eval.zip
/lambda/game_data/game_data.zip
/lambda/reflections/reflections.zip
# Any extra javascript files for lambda functions
!lambda/code_eval/*.js
1 change: 1 addition & 0 deletions LearningLens2025/frontend/.example.env
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ GOOGLE_CLIENT_ID=
AI_LOGGING_URL=
CODE_EVAL_URL=
GAME_URL=
REFLECTIONS_URL=
LOCAL_MODEL_DOWNLOAD_URL_PATH=
Original file line number Diff line number Diff line change
Expand Up @@ -928,8 +928,10 @@ class MoodleLmsService implements LmsInterface {
return [];
}

final data = jsonDecode(response.body) as List<dynamic>;
if (data.isEmpty || data.first is! Map<String, dynamic>) {
final data = jsonDecode(response.body);
if (data.isEmpty ||
data is! List<dynamic> ||
data.first is! Map<String, dynamic>) {
return [];
}
print('Rubric Grades Response: ${jsonEncode(data)}');
Expand Down
6 changes: 5 additions & 1 deletion LearningLens2025/frontend/lib/Views/dashboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:learninglens_app/Views/iep_page.dart';
import 'package:learninglens_app/Views/lesson_plans.dart';
import 'package:learninglens_app/Views/nav_card.dart';
import 'package:learninglens_app/Views/program_assessment_view.dart';
import 'package:learninglens_app/Views/student_reflections_page.dart';
import 'package:learninglens_app/Views/user_settings.dart';
import 'package:learninglens_app/services/local_storage_service.dart';

Expand Down Expand Up @@ -361,7 +362,10 @@ class TeacherDashboard extends StatelessWidget {
{
'title': 'Reflections',
'description': 'Reflect on your use of AI for your assignments.',
'onPressed': null,
'onPressed': () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StudentReflectionsPage())),
'icon': Icons.note_add_outlined
},
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
import 'package:learninglens_app/Api/lms/factory/lms_factory.dart';
import 'package:learninglens_app/Controller/custom_appbar.dart';

class EditReflectionQuestionsPage extends StatefulWidget {
final String assignmentId;
final String courseId;
final List<String> initialQuestions;

const EditReflectionQuestionsPage({
Key? key,
required this.assignmentId,
required this.courseId,
required this.initialQuestions,
}) : super(key: key);

@override
State<EditReflectionQuestionsPage> createState() =>
_EditReflectionQuestionsPageState();
}

class _EditReflectionQuestionsPageState
extends State<EditReflectionQuestionsPage> {
List<TextEditingController> _controllers = [];
final _formKey = GlobalKey<FormState>();

@override
void initState() {
super.initState();
_controllers = widget.initialQuestions
.map((q) => TextEditingController(text: q))
.toList();
if (_controllers.isEmpty) {
_controllers.add(TextEditingController());
}
}

void addQuestionField() {
setState(() {
_controllers.add(TextEditingController());
});
}

void removeQuestionField(int index) {
setState(() {
_controllers.removeAt(index);
});
}

Future<void> saveQuestions() async {
if (!_formKey.currentState!.validate()) return;

List<String> questions =
_controllers.map((controller) => controller.text.trim()).toList();

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Questions saved successfully!')),
);
Navigator.pop(context, questions);
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
title: 'Edit Reflection Questions',
userprofileurl: LmsFactory.getLmsService().profileImage ?? '',
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Reflection Questions',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 12),
..._controllers.asMap().entries.map(
(entry) {
int index = entry.key;
TextEditingController controller = entry.value;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6.0),
child: Row(
children: [
Expanded(
child: TextFormField(
controller: controller,
decoration: InputDecoration(
labelText: 'Question ${index + 1}',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter a question';
}
return null;
},
),
),
SizedBox(width: 8),
if (_controllers.length > 1)
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () => removeQuestionField(index),
),
],
),
);
},
),
SizedBox(height: 12),
Row(
children: [
ElevatedButton.icon(
onPressed: addQuestionField,
icon: Icon(Icons.add),
label: Text('Add Question'),
),
SizedBox(width: 12),
ElevatedButton(
onPressed: saveQuestions,
child: Text('Save Questions'),
),
],
),
],
),
),
),
);
}
}
44 changes: 24 additions & 20 deletions LearningLens2025/frontend/lib/Views/gamification_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ class _GamificationViewState extends State<GamificationView> {
});
} else {
games = await _gamificationService.getGamesForStudent(userId);
final completed = games.where((g) => g.score != null).toList();
final pending = games.where((g) => g.score == null).toList();
final completed = games.where((g) => g.score!.score != null).toList();
final pending = games.where((g) => g.score!.score == null).toList();
await _ensureCourseNames();
setState(() {
assignedGames = pending;
Expand Down Expand Up @@ -906,19 +906,22 @@ $text
});

try {
final assignedGame = AssignedGame(
uuid: null,
courseId: courseId,
gameType: gameTypeEnum,
title: title,
gameData: contentPayload,
assignedDate: now,
assignedBy: teacherId,
);
final gameResponse = await _gamificationService.createGame(assignedGame);
final responseBody = jsonDecode(gameResponse.body);
final gameId = responseBody[0]["game_id"];

await Future.wait(targetStudents.map((student) {
final assignedGame = AssignedGame(
uuid: null,
studentId: student.id,
courseId: courseId,
gameType: gameTypeEnum,
title: title,
gameData: contentPayload,
assignedDate: now,
assignedBy: teacherId,
studentName: '${student.firstname} ${student.lastname}'.trim(),
);
return _gamificationService.createGame(assignedGame);
return _gamificationService
.assignGame(AssignedGameScore(studentId: student.id, game: gameId));
}));

await _refreshAssignments();
Expand Down Expand Up @@ -1303,6 +1306,7 @@ $text
try {
final response = await _gamificationService.completeGame(
game.uuid!,
game.score!.studentId,
normalizedScore,
rawCorrect: result.score,
maxScore: result.maxScore,
Expand Down Expand Up @@ -1447,7 +1451,7 @@ $text
final groupedByStudent = <int, List<AssignedGame>>{};
for (final game in games) {
groupedByStudent
.putIfAbsent(game.studentId, () => [])
.putIfAbsent(game.score!.studentId, () => [])
.add(game);
}

Expand All @@ -1459,13 +1463,13 @@ $text
final studentGames = entry.value
..sort((a, b) => b.assignedDate.compareTo(a.assignedDate));
for (final game in studentGames) {
final hasRaw = game.rawCorrect != null &&
game.maxScore != null &&
game.maxScore! > 0;
final isCompleted = game.score != null;
final hasRaw = game.score!.rawCorrect != null &&
game.score!.maxScore != null &&
game.score!.maxScore! > 0;
final isCompleted = game.score!.score != null;
final statusText = isCompleted
? hasRaw
? 'Completed ${game.rawCorrect}/${game.maxScore}'
? 'Completed ${game.score!.rawCorrect}/${game.score!.maxScore}'
: 'Completed'
: 'Pending';
rows.add(
Expand Down
66 changes: 63 additions & 3 deletions LearningLens2025/frontend/lib/Views/send_essay_to_moodle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import 'package:learninglens_app/Api/lms/factory/lms_factory.dart';
import 'package:learninglens_app/Controller/custom_appbar.dart';
import 'package:learninglens_app/beans/course.dart';
import 'package:learninglens_app/Views/dashboard.dart';
import 'package:learninglens_app/Views/edit_reflection_questions_page.dart';
import 'dart:convert';

import 'package:learninglens_app/services/reflection_service.dart';

class EssayAssignmentSettings extends StatefulWidget {
final String updatedJson;
final String description;
Expand All @@ -18,6 +21,7 @@ class EssayAssignmentSettings extends StatefulWidget {
class EssayAssignmentSettingsState extends State<EssayAssignmentSettings> {
// Global key for the form
final _formKey = GlobalKey<FormState>();
List<String> reflectionQuestions = [];

// Date selection variables for "Allow submissions from"
String selectedDaySubmission = '01';
Expand Down Expand Up @@ -232,8 +236,7 @@ class EssayAssignmentSettingsState extends State<EssayAssignmentSettings> {
double screenWidth = constraints.maxWidth;

// Example: Calculate sizes dynamically based on screen width
double buttonWidth =
screenWidth * 0.4; // Buttons take 40% of screen width
double buttonWidth = 275.0;
double descriptionHeight = screenWidth *
0.2; // Description box takes 20% of screen width height

Expand Down Expand Up @@ -471,7 +474,7 @@ class EssayAssignmentSettingsState extends State<EssayAssignmentSettings> {
String allowSubmissionFrom =
'$selectedDaySubmission $selectedMonthSubmission $selectedYearSubmission $selectedHourSubmission:$selectedMinuteSubmission';

await api.createAssignment(
final result = await api.createAssignment(
courseId,
sectionNumber, // Section ID
assignmentName,
Expand All @@ -481,6 +484,16 @@ class EssayAssignmentSettingsState extends State<EssayAssignmentSettings> {
description,
);

print(result);

for (String r in reflectionQuestions) {
await ReflectionService().createReflection(
Reflection(
courseId: int.parse(courseId),
assignmentId: result?['assignmentid'],
question: r));
}

if (mounted) {
final snackBar = SnackBar(
content: Text(
Expand Down Expand Up @@ -526,6 +539,53 @@ class EssayAssignmentSettingsState extends State<EssayAssignmentSettings> {
child: Text('Go Back to Edit Essay'),
),
),
SizedBox(
width: buttonWidth,
child: ElevatedButton.icon(
onPressed: () async {
// Get course ID from selected course
Course? selectedCourseObj = courses.firstWhere(
(c) => c.fullName == selectedCourse,
orElse: () => Course(0, '', '', '',
DateTime.now(), DateTime.now()));
if (selectedCourseObj.id == 0) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text('Please select a course first.')),
);
return;
}

String assignmentId =
_assignmentNameController.text.isNotEmpty
? _assignmentNameController.text
: 'temp_assignment';

final updatedQuestions = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
EditReflectionQuestionsPage(
courseId: selectedCourseObj.id.toString(),
assignmentId: assignmentId,
initialQuestions: [],
),
),
);

if (updatedQuestions != null &&
updatedQuestions is List<String>) {
// Store the questions locally in state
setState(() {
reflectionQuestions = updatedQuestions;
});
}
},
icon: Icon(Icons.edit),
label: Text('Edit Reflection Questions'),
),
),
],
),
],
Expand Down
Loading