From 419c88701e0b0d963e2a5a8701022bd62f0ce580 Mon Sep 17 00:00:00 2001 From: Ryan Appleby Date: Mon, 27 Oct 2025 23:51:26 -0400 Subject: [PATCH 1/6] classroom start --- .../google_classroom_api.dart | 2 +- .../google_classroom/google_lms_service.dart | 148 ++++++++++++++++-- .../frontend/lib/Api/lms/lms_interface.dart | 29 ++++ .../Api/lms/moodle/moodle_lms_service.dart | 8 +- .../lib/Api/lms/template/api_singleton.dart | 23 +++ .../lib/Views/g_assignment_create.dart | 66 ++------ .../frontend/lib/Views/iep_page.dart | 34 ++-- .../frontend/lib/beans/assignment.dart | 11 +- LearningLens2025/frontend/lib/beans/quiz.dart | 7 + 9 files changed, 237 insertions(+), 91 deletions(-) diff --git a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_classroom_api.dart b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_classroom_api.dart index 05e82524..53269ef0 100644 --- a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_classroom_api.dart +++ b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_classroom_api.dart @@ -201,7 +201,7 @@ class GoogleClassroomApi { int day = int.parse(dateParts[2]); int hours = int.parse(dateParts[3]); int minutes = int.parse(dateParts[4]); - String? topicId = await getTopicId(courseId, 'Quiz') ?? '755868506953'; + String? topicId = await getTopicId(courseId, 'quiz') ?? '755868506953'; print('topic id is : $topicId'); diff --git a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart index 3411f358..6137998f 100644 --- a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart +++ b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart @@ -1,17 +1,21 @@ import 'dart:convert'; import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:http/http.dart' as http; import 'package:learninglens_app/Api/lms/google_classroom/google_classroom_api.dart'; // Import the updated API import 'package:learninglens_app/Api/lms/lms_interface.dart'; +import 'package:learninglens_app/Views/assessments_view.dart'; import 'package:learninglens_app/beans/assignment.dart'; import 'package:learninglens_app/beans/course.dart'; import 'package:learninglens_app/beans/g_question_form_data.dart'; import 'package:learninglens_app/beans/grade.dart'; import 'package:learninglens_app/beans/moodle_rubric.dart'; +import 'package:learninglens_app/beans/override.dart'; import 'package:learninglens_app/beans/participant.dart'; import 'package:learninglens_app/beans/quiz.dart'; +import 'package:learninglens_app/beans/quiz_override'; import 'package:learninglens_app/beans/quiz_type.dart'; import 'package:learninglens_app/beans/submission.dart'; import 'package:learninglens_app/beans/submission_status.dart'; @@ -64,8 +68,11 @@ class GoogleLmsService extends LmsInterface { String? profileImage; @override List? courses; + @override + List? overrides; late GoogleSignIn _googleSignIn; + // **************************************************************************************** // Auth / Login @@ -257,11 +264,11 @@ class GoogleLmsService extends LmsInterface { // Iterate and capture topicIds for (var topic in topics) { - if (topic['name'] == 'Quiz') { + if (topic['name'] == 'quiz') { course.quizTopicId = int.parse(topic['topicId']); // print('Id for quiz'); // print(topic['topicId']); - } else if (topic['name'] == 'Essay') { + } else if (topic['name'] == 'essay') { course.essayTopicId = int.parse(topic['topicId']); // print('Id for essay'); // print(topic['topicId']); @@ -336,6 +343,8 @@ class GoogleLmsService extends LmsInterface { headers: {'Authorization': 'Bearer $_userToken'}, ); + topicId ??= courses?.firstWhere((c) => c.id == courseID).quizTopicId; + // print('quizlist: ${response.body}'); if (response.statusCode != 200) { @@ -372,7 +381,7 @@ class GoogleLmsService extends LmsInterface { @override Future createQuiz(String courseid, String quizname, String quizintro, - String sectionid, String timeopen, String timeclose) async { + String sectionid, String timeopen, String timeclose, {List individualStudentsOptions = const []}) async { print('Creating quiz in Google Classroom...'); print('Course ID: $courseid'); print('Quiz Name: $quizname'); @@ -386,7 +395,7 @@ class GoogleLmsService extends LmsInterface { String formattedTimeOpen = DateTime.parse(timeopen).toIso8601String(); String? assignmentId = await createAssignmentHelper( - courseid, quizname, quizintro, sectionid, formattedTimeOpen); + courseid, quizname, quizintro, sectionid, formattedTimeOpen, individualStudentsOptions); if (assignmentId != null) { return int.parse(assignmentId); @@ -401,7 +410,7 @@ class GoogleLmsService extends LmsInterface { } Future createAssignmentHelper(String courseId, String title, - String description, String responderUri, String dueDate) async { + String description, String responderUri, String dueDate, List individualStudentsOptions) async { print('Creating assignment in Google Classroom... Inside helper'); print('Course ID: $courseId'); print('Title: $title'); @@ -425,7 +434,7 @@ class GoogleLmsService extends LmsInterface { // Parse the dueDate string DateTime parsedDate = DateTime.parse(dueDate); - final body = jsonEncode({ + var requestBody = { "title": title, "description": description, "workType": "ASSIGNMENT", @@ -445,7 +454,18 @@ class GoogleLmsService extends LmsInterface { "link": {"url": responderUri} } ] - }); + + }; + + + if (individualStudentsOptions.isNotEmpty) { + requestBody['assigneeMode'] = "INDIVIDUAL_STUDENTS"; + requestBody['individualStudentsOptions'] = { + "studentIds" : individualStudentsOptions.map((e) => e.toString()).toList() + } ; + } + + final body = jsonEncode(requestBody); // Print request details print('Request URL: $url'); @@ -711,6 +731,8 @@ class GoogleLmsService extends LmsInterface { throw StateError('User not logged in to Google Classroom'); } + topicId ??= courses?.firstWhere((c) => c.id == courseID).essayTopicId; + final response = await ApiService().httpGet( Uri.parse( 'https://classroom.googleapis.com/v1/courses/$courseID/courseWork'), @@ -752,15 +774,63 @@ class GoogleLmsService extends LmsInterface { String enddate, String rubricJson, String description, + {List individualStudentsOptions = const []} ) async { - print('Creating assignment...'); - print('Course ID: $courseid'); - print('Section ID: $sectionid'); - print('Assignment Name: $assignmentName'); - print('Start Date: $startdate'); - print('End Date: $enddate'); - // TODO: implement google api code - throw UnimplementedError(); + final url = Uri.parse( + 'https://classroom.googleapis.com/v1/courses/$courseid/courseWork'); + final headers = { + 'Authorization': 'Bearer $_userToken', + 'Content-Type': 'application/json', + }; + + Map requestBody = { + 'title': assignmentName, + 'description': description, + 'state': 'PUBLISHED', + 'workType': 'ASSIGNMENT', + 'maxPoints': int.tryParse(rubricJson.toString()), + }; + + DateTime d = DateTime.fromMillisecondsSinceEpoch(int.parse(enddate)); + + if (d != null) { + requestBody['dueDate'] = { + 'year': d.year, + 'month': d.month, + 'day': d.day, + }; + requestBody['dueTime'] = { + 'hours': d.hour, + 'minutes': d.minute, + 'seconds': 0, + }; + } + + if (individualStudentsOptions.isNotEmpty) { + requestBody['assigneeMode'] = "INDIVIDUAL_STUDENTS"; + requestBody['individualStudentsOptions'] = { + "studentIds" : individualStudentsOptions.map((e) => e.toString()).toList() + }; + } + + String? topicIdNew = await GoogleClassroomApi().getTopicId(courseid, "essay"); + if (topicIdNew != null) { + requestBody['topicId'] = topicIdNew; + } + + final body = jsonEncode(requestBody); + + final response = await ApiService().httpPost(url, headers: headers, body: body); + + if (response.statusCode != 200) { + print('Request failed with status: ${response.statusCode}.'); + return null; + } + + final responseData = jsonDecode(response.body) as Map; + print('Create Assignment Response: $responseData'); + + return responseData; } @override @@ -1145,4 +1215,52 @@ class GoogleLmsService extends LmsInterface { // TODO: implement uploadFileToDraft throw UnimplementedError(); } + + @override + Future refreshOverrides() async { + List override = []; + if (courses != null) { + for (Course c in courses!) { + List parts = await getCourseParticipants(c.id.toString()); + var quizzes = (await getQuizzes(c.id, topicId: c.quizTopicId)).where((q) => q.individualStudentsOptions.isNotEmpty); + for (Quiz q in quizzes) { + for (int p in q.individualStudentsOptions) { + override.add(Override(override.length, "quiz", q.id!, q.name!, c.id, c.fullName, p, parts.firstWhere((part) => part.id == p).fullname, q.timeClose, null, q.timeClose, 0)); + } + } + var essays = (await getEssays(c.id, topicId: c.essayTopicId)).where((e) => e.individualStudentsOptions.isNotEmpty); + for (Assignment e in essays) { + for (int p in e.individualStudentsOptions) { + override.add(Override(override.length, "essay", e.id, e.name, c.id, c.fullName, p, parts.firstWhere((part) => part.id == p).fullname, e.dueDate, null, e.cutoffDate, 0)); + } + } + } + } + } + + @override + Future addEssayOverride({required int assignid, int? courseId, int? userId, int? groupId, int? allowsubmissionsfromdate, int? dueDate, int? cutoffDate, int? timelimit, int? sortorder}) async { + if (courseId == null) { return "";} + var assignment = getCourse(courseId).essays?.firstWhereOrNull((e) => e.id == assignid); + if (assignment == null) { + print(getCourse(courseId).essays); + return ""; + } + print("due date: $dueDate"); + var response = await createAssignment(courseId.toString(), "", assignment.name, (assignment.allowsubmissionsfromdate?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch).toString(), (dueDate == null ? assignment.dueDate?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch : dueDate * 1000).toString(), assignment.maxScore.toString(), assignment.description, individualStudentsOptions: [userId!]); + print(response); + return "Created essay override"; + } + + @override + Future addQuizOverride({required int quizId, int? courseId, int? userId, int? groupId, int? timeOpen, int? timeClose, int? timeLimit, int? attempts, String? password}) async { + if (courseId == null) { return QuizOverride.empty();} + var assignment = getCourse(courseId).quizzes?.firstWhereOrNull((e) => e.id == quizId); + if (assignment == null) { + return QuizOverride.empty(); + } + + await createQuiz(courseId.toString(), assignment.name!, assignment.description ?? "", "", (timeOpen == null ? assignment.timeOpen : DateTime.fromMillisecondsSinceEpoch(timeOpen)).toString(), (timeClose == null ? assignment.timeClose?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch : timeClose * 1000).toString(), individualStudentsOptions: [userId!]); + return QuizOverride.empty(); + } } diff --git a/LearningLens2025/frontend/lib/Api/lms/lms_interface.dart b/LearningLens2025/frontend/lib/Api/lms/lms_interface.dart index 3320031d..95ae67fb 100644 --- a/LearningLens2025/frontend/lib/Api/lms/lms_interface.dart +++ b/LearningLens2025/frontend/lib/Api/lms/lms_interface.dart @@ -4,8 +4,10 @@ import 'package:learninglens_app/beans/assignment.dart'; import 'package:learninglens_app/beans/course.dart'; import 'package:learninglens_app/beans/grade.dart'; import 'package:learninglens_app/beans/moodle_rubric.dart'; +import 'package:learninglens_app/beans/override.dart'; import 'package:learninglens_app/beans/participant.dart'; import 'package:learninglens_app/beans/quiz.dart'; +import 'package:learninglens_app/beans/quiz_override'; import 'package:learninglens_app/beans/quiz_type.dart'; import 'package:learninglens_app/beans/submission.dart'; import 'package:learninglens_app/beans/submission_status.dart'; @@ -27,6 +29,7 @@ abstract class LmsInterface { String? profileImage; List? courses; UserRole? role; + List? overrides; // Authentication/Login methods Future login(String username, String password, String baseURL); @@ -104,6 +107,8 @@ abstract class LmsInterface { required int draftItemId, }); + Future refreshOverrides(); + Future submitAssignmentForGrading({ required int assignId, bool acceptSubmissionStatement, @@ -114,4 +119,28 @@ abstract class LmsInterface { }) { throw UnimplementedError(); } + + Future addQuizOverride({ + required int quizId, + int? userId, + int? groupId, + int? timeOpen, + int? timeClose, + int? timeLimit, + int? attempts, + String? password, + int? courseId + }); + + Future addEssayOverride({ + required int assignid, + int? userId, + int? groupId, + int? allowsubmissionsfromdate, + int? dueDate, + int? cutoffDate, + int? timelimit, + int? sortorder, + int? courseId + }); } diff --git a/LearningLens2025/frontend/lib/Api/lms/moodle/moodle_lms_service.dart b/LearningLens2025/frontend/lib/Api/lms/moodle/moodle_lms_service.dart index a1370291..150b28e0 100644 --- a/LearningLens2025/frontend/lib/Api/lms/moodle/moodle_lms_service.dart +++ b/LearningLens2025/frontend/lib/Api/lms/moodle/moodle_lms_service.dart @@ -62,10 +62,12 @@ class MoodleLmsService implements LmsInterface { @override UserRole? role; - List? overrides; - int? userId; + + @override + List? overrides; + String? get userToken => _userToken; // **************************************************************************************** @@ -1019,6 +1021,7 @@ class MoodleLmsService implements LmsInterface { int? timeLimit, int? attempts, String? password, + int? courseId }) async { if (_userToken == null) throw StateError('User not logged in to Moodle'); @@ -1061,6 +1064,7 @@ class MoodleLmsService implements LmsInterface { int? cutoffDate, int? timelimit, int? sortorder, + int? courseId }) async { if (_userToken == null) throw StateError('User not logged in to Moodle'); diff --git a/LearningLens2025/frontend/lib/Api/lms/template/api_singleton.dart b/LearningLens2025/frontend/lib/Api/lms/template/api_singleton.dart index ad7e598c..77516cc7 100644 --- a/LearningLens2025/frontend/lib/Api/lms/template/api_singleton.dart +++ b/LearningLens2025/frontend/lib/Api/lms/template/api_singleton.dart @@ -6,8 +6,10 @@ import 'package:learninglens_app/beans/course.dart'; import 'package:learninglens_app/beans/grade.dart'; import 'package:learninglens_app/beans/lesson_plan.dart'; import 'package:learninglens_app/beans/moodle_rubric.dart'; +import 'package:learninglens_app/beans/override.dart'; import 'package:learninglens_app/beans/participant.dart'; import 'package:learninglens_app/beans/quiz.dart'; +import 'package:learninglens_app/beans/quiz_override'; import 'package:learninglens_app/beans/quiz_type.dart'; import 'package:learninglens_app/beans/submission.dart'; import 'package:learninglens_app/beans/submission_status.dart'; @@ -54,6 +56,9 @@ class ApiSingleton implements LmsInterface { @override List? courses; + @override + List? overrides; + // Authentication/Login methods @override Future login(String username, String password, String baseURL) { @@ -307,4 +312,22 @@ class ApiSingleton implements LmsInterface { // TODO: implement getSubmissionStatusRaw throw UnimplementedError(); } + + @override + Future refreshOverrides() { + // TODO: implement refreshOverrides + throw UnimplementedError(); + } + + @override + Future addEssayOverride({required int assignid, int? userId, int? groupId, int? allowsubmissionsfromdate, int? dueDate, int? cutoffDate, int? timelimit, int? sortorder, int? courseId}) { + // TODO: implement addEssayOverride + throw UnimplementedError(); + } + + @override + Future addQuizOverride({required int quizId, int? userId, int? groupId, int? timeOpen, int? timeClose, int? timeLimit, int? attempts, String? password, int? courseId}) { + // TODO: implement addQuizOverride + throw UnimplementedError(); + } } diff --git a/LearningLens2025/frontend/lib/Views/g_assignment_create.dart b/LearningLens2025/frontend/lib/Views/g_assignment_create.dart index e76694a3..34d103de 100644 --- a/LearningLens2025/frontend/lib/Views/g_assignment_create.dart +++ b/LearningLens2025/frontend/lib/Views/g_assignment_create.dart @@ -22,7 +22,7 @@ class _CreateAssignmentPageState extends State { int? _points; DateTime? _dueDate; TimeOfDay? _dueTime; - final String _topic = 'Essay'; // Made static with fixed value + final String _topic = 'essay'; // Made static with fixed value String? _title; String? _instructions; List _courses = []; @@ -110,71 +110,25 @@ class _CreateAssignmentPageState extends State { return; } - final url = Uri.parse( - 'https://classroom.googleapis.com/v1/courses/$_selectedCourseId/courseWork'); - final headers = { - 'Authorization': 'Bearer $token', - 'Content-Type': 'application/json', - }; - - Map requestBody = { - 'title': _title, - 'description': _instructions, - 'state': 'PUBLISHED', - 'workType': 'ASSIGNMENT', - 'maxPoints': _points, - }; - - if (_dueDate != null && _dueTime != null) { - requestBody['dueDate'] = { - 'year': _dueDate!.year, - 'month': _dueDate!.month, - 'day': _dueDate!.day, - }; - requestBody['dueTime'] = { - 'hours': _dueTime!.hour, - 'minutes': _dueTime!.minute, - 'seconds': 0, - }; - } - - String? topicIdNew = await widget._googleClassroomApi - .getTopicId(_selectedCourseId!, _topic); - if (topicIdNew != null) { - requestBody['topicId'] = topicIdNew; - } - - final body = jsonEncode(requestBody); - - try { - final response = await http.post(url, headers: headers, body: body); - - if (response.statusCode == 200) { - print('Assignment created successfully!'); + var ess = await GoogleLmsService().createAssignment(_selectedCourseId!, "", _title!, _dueDate?.millisecondsSinceEpoch.toString() ?? "", "", _points?.toString() ?? "", _instructions ?? ""); + if (ess != null) { + print('Assignment created successfully!'); await GoogleLmsService() .courses ?.firstWhere((c) => c.id.toString() == _selectedCourseId) - .refreshQuizzes(); + .refreshEssays(); Navigator.pop(context); - } else { - ScaffoldMessenger.of(context).showSnackBar( + } + else { + ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: - Text('Error creating assignment: ${response.statusCode}'), + Text('Error creating assignment.'), backgroundColor: Colors.red, ), ); - } - } catch (e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Network error: ${e.toString()}'), - backgroundColor: Colors.red, - ), - ); - } finally { - setState(() => _isSubmitting = false); } +setState(() => _isSubmitting = false); } } diff --git a/LearningLens2025/frontend/lib/Views/iep_page.dart b/LearningLens2025/frontend/lib/Views/iep_page.dart index e7d5149b..da44d971 100644 --- a/LearningLens2025/frontend/lib/Views/iep_page.dart +++ b/LearningLens2025/frontend/lib/Views/iep_page.dart @@ -61,7 +61,7 @@ class _IepPageState extends State { @override void initState() { super.initState(); - overrides = MoodleLmsService().overrides; + overrides = LmsFactory.getLmsService().overrides; overrides?.sort((a, b) => a.fullname.compareTo(b.fullname)); selectedLLM = LlmType.values .firstWhereOrNull((llm) => LocalStorageService.userHasLlmKey(llm)); @@ -561,13 +561,13 @@ class _IepPageState extends State { attempts != null) && (selectedAssignment?.type != "essay" || epochTime2 != null) - ? () { + ? () async { if (selectedAssignment?.type == 'quiz') { - quizOver(epochTime!, selectedAssignment!.id, + await quizOver(epochTime!, int.parse(selectedCourse!), selectedAssignment!.id, userId!, attempts!); } else if (selectedAssignment?.type == 'essay') { - essayOver(epochTime!, selectedAssignment!.id, + await essayOver(epochTime!, int.parse(selectedCourse!), selectedAssignment!.id, userId!, epochTime2!); } resetForm(false); @@ -697,13 +697,13 @@ class _IepPageState extends State { List? getAllCourses() { List? result; - result = MoodleLmsService().courses; + result = LmsFactory.getLmsService().courses; return result; } Future>? getAllParticipants(String courseID) async { List? participants; - participants = await MoodleLmsService().getCourseParticipants(courseID); + participants = await LmsFactory.getLmsService().getCourseParticipants(courseID); return participants; } @@ -723,11 +723,11 @@ class _IepPageState extends State { Future> handleAssessmentSelection(int? courseID) async { if (courseID != null) { - List essayList = await MoodleLmsService().getEssays(courseID); + List essayList = await LmsFactory.getLmsService().getEssays(courseID); // Fetch quizzes (if available). List quizList = []; try { - quizList = await MoodleLmsService().getQuizzes(courseID); + quizList = await LmsFactory.getLmsService().getQuizzes(courseID); } catch (e) { print("getQuizzes not available or failed: $e"); } @@ -760,15 +760,16 @@ class _IepPageState extends State { }); } - void quizOver(int epochTime, int quizId, int userId, int attempts) async { - await MoodleLmsService().addQuizOverride( + Future quizOver(int epochTime, int courseId, int quizId, int userId, int attempts) async { + await LmsFactory.getLmsService().addQuizOverride( quizId: quizId, + courseId: courseId, userId: userId, timeClose: epochTime, attempts: attempts); - await MoodleLmsService().refreshOverrides(); + await LmsFactory.getLmsService().refreshOverrides(); setState(() { - overrides = MoodleLmsService().overrides; + overrides = LmsFactory.getLmsService().overrides; overrides?.sort((a, b) => a.fullname.compareTo(b.fullname)); }); ScaffoldMessenger.of(context).showSnackBar( @@ -776,15 +777,16 @@ class _IepPageState extends State { ); } - void essayOver(int epochTime, int essayId, int userId, int epochTime2) async { - await MoodleLmsService().addEssayOverride( + Future essayOver(int epochTime, int courseId, int essayId, int userId, int epochTime2) async { + await LmsFactory.getLmsService().addEssayOverride( assignid: essayId, + courseId: courseId, userId: userId, dueDate: epochTime, cutoffDate: epochTime2); - await MoodleLmsService().refreshOverrides(); + await LmsFactory.getLmsService().refreshOverrides(); setState(() { - overrides = MoodleLmsService().overrides; + overrides = LmsFactory.getLmsService().overrides; overrides?.sort((a, b) => a.fullname.compareTo(b.fullname)); }); ScaffoldMessenger.of(context).showSnackBar( diff --git a/LearningLens2025/frontend/lib/beans/assignment.dart b/LearningLens2025/frontend/lib/beans/assignment.dart index 1ee20924..5ec59edb 100644 --- a/LearningLens2025/frontend/lib/beans/assignment.dart +++ b/LearningLens2025/frontend/lib/beans/assignment.dart @@ -15,6 +15,9 @@ class Assignment implements LearningLensInterface { final List? submissionsWithGrades; + List individualStudentsOptions = []; + int? maxScore; + Assignment({ required this.id, required this.name, @@ -67,7 +70,7 @@ class Assignment implements LearningLensInterface { @override Assignment fromGoogleJson(Map json) { - return Assignment( + Assignment a = Assignment( id: int.parse(json['id']), name: json['title'] ?? 'Untitled', description: json['description'] ?? '', @@ -87,6 +90,12 @@ class Assignment implements LearningLensInterface { gradingStatus: 0, // TODO: figure out grading status courseId: int.parse(json['courseId']), ); + if (json['AssigneeMode']?.toString() == "INDIVIDUAL_STUDENTS") { + final studentOptions = json["studentIds"] as List; + a.individualStudentsOptions.addAll(studentOptions.map((e) => int.parse(e.toString()))); + } + a.maxScore = json["maxPoints"]; + return a; } @override diff --git a/LearningLens2025/frontend/lib/beans/quiz.dart b/LearningLens2025/frontend/lib/beans/quiz.dart index 95e69620..9776d1c6 100644 --- a/LearningLens2025/frontend/lib/beans/quiz.dart +++ b/LearningLens2025/frontend/lib/beans/quiz.dart @@ -1,3 +1,4 @@ +import 'package:learninglens_app/Controller/g_bean.dart'; import 'package:xml/xml.dart'; import 'package:learninglens_app/beans/question.dart'; import 'package:learninglens_app/beans/xml_consts.dart'; @@ -13,6 +14,8 @@ class Quiz { DateTime? timeOpen; DateTime? timeClose; + + List individualStudentsOptions = []; // Constructor with all optional params. Quiz( {this.name, @@ -100,6 +103,10 @@ class Quiz { print('Debug: DueDate parsed to: ${tmpQuiz.timeClose}'); print('Debug: Quiz object created successfully'); + if (json['AssigneeMode']?.toString() == "INDIVIDUAL_STUDENTS") { + final studentOptions = json["studentIds"] as List; + tmpQuiz.individualStudentsOptions.addAll(studentOptions.map((e) => int.parse(e.toString()))); + } return tmpQuiz; } From 988a96d33e5ffa70029a16de820afff63016a2a7 Mon Sep 17 00:00:00 2001 From: Ryan Appleby Date: Tue, 28 Oct 2025 01:46:33 -0400 Subject: [PATCH 2/6] commit for later --- .../google_classroom/google_lms_service.dart | 86 +++++++++++++++---- .../lib/Views/g_assignment_create.dart | 2 +- .../frontend/lib/services/api_service.dart | 28 ++++++ 3 files changed, 97 insertions(+), 19 deletions(-) diff --git a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart index 6137998f..28810bf7 100644 --- a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart +++ b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart @@ -304,8 +304,7 @@ class GoogleLmsService extends LmsInterface { if (studentsJson.containsKey('students')) { for (var student in studentsJson['students']) { participants.add(Participant( - id: student['userId'] - .hashCode, // Google Classroom does not provide numeric IDs + id: int.parse(student['userId']), fullname: student['profile']['name']['fullName'], firstname: student['profile']['name']['givenName'], lastname: student['profile']['name']['familyName'], @@ -381,7 +380,7 @@ class GoogleLmsService extends LmsInterface { @override Future createQuiz(String courseid, String quizname, String quizintro, - String sectionid, String timeopen, String timeclose, {List individualStudentsOptions = const []}) async { + String sectionid, String timeopen, String timeclose, {List individualStudentsOptions = const []}) async { print('Creating quiz in Google Classroom...'); print('Course ID: $courseid'); print('Quiz Name: $quizname'); @@ -410,7 +409,7 @@ class GoogleLmsService extends LmsInterface { } Future createAssignmentHelper(String courseId, String title, - String description, String responderUri, String dueDate, List individualStudentsOptions) async { + String description, String responderUri, String dueDate, List individualStudentsOptions) async { print('Creating assignment in Google Classroom... Inside helper'); print('Course ID: $courseId'); print('Title: $title'); @@ -459,10 +458,9 @@ class GoogleLmsService extends LmsInterface { if (individualStudentsOptions.isNotEmpty) { - requestBody['assigneeMode'] = "INDIVIDUAL_STUDENTS"; requestBody['individualStudentsOptions'] = { - "studentIds" : individualStudentsOptions.map((e) => e.toString()).toList() - } ; + "studentIds" : individualStudentsOptions + }; } final body = jsonEncode(requestBody); @@ -774,7 +772,7 @@ class GoogleLmsService extends LmsInterface { String enddate, String rubricJson, String description, - {List individualStudentsOptions = const []} + {List individualStudentsOptions = const []} ) async { final url = Uri.parse( 'https://classroom.googleapis.com/v1/courses/$courseid/courseWork'); @@ -786,7 +784,7 @@ class GoogleLmsService extends LmsInterface { Map requestBody = { 'title': assignmentName, 'description': description, - 'state': 'PUBLISHED', + 'state': individualStudentsOptions.isNotEmpty ? 'DRAFT' : 'PUBLISHED', 'workType': 'ASSIGNMENT', 'maxPoints': int.tryParse(rubricJson.toString()), }; @@ -806,18 +804,13 @@ class GoogleLmsService extends LmsInterface { }; } - if (individualStudentsOptions.isNotEmpty) { - requestBody['assigneeMode'] = "INDIVIDUAL_STUDENTS"; - requestBody['individualStudentsOptions'] = { - "studentIds" : individualStudentsOptions.map((e) => e.toString()).toList() - }; - } - String? topicIdNew = await GoogleClassroomApi().getTopicId(courseid, "essay"); if (topicIdNew != null) { requestBody['topicId'] = topicIdNew; } + print(requestBody); + final body = jsonEncode(requestBody); final response = await ApiService().httpPost(url, headers: headers, body: body); @@ -830,6 +823,60 @@ class GoogleLmsService extends LmsInterface { final responseData = jsonDecode(response.body) as Map; print('Create Assignment Response: $responseData'); + if (individualStudentsOptions.isNotEmpty) { + var id = responseData['id']; + final modUrl = Uri.parse( + 'https://classroom.googleapis.com/v1/courses/$courseid/courseWork/$id:modifyAssignees'); + final modHead = { + 'Authorization': 'Bearer $_userToken', + 'Content-Type': 'application/json', + }; + + Map modifyAssigneesBody = { + 'assigneeMode': 'INDIVIDUAL_STUDENTS', + 'modifyIndividualStudentsOptions': { + "addStudentIds" : individualStudentsOptions, + "removeStudentIds" : [] + } + }; + final modBody = jsonEncode(modifyAssigneesBody); + + final modRep = await ApiService().httpPost(modUrl, headers: modHead, body: modBody); + + if (modRep.statusCode != 200) { + print('Request failed with status: ${modRep.statusCode}.'); + return null; + } + + var modData = jsonDecode(modRep.body) as Map; + print('Modify Assignment Response: $modData'); + + + final patchUrl = Uri.parse( + 'https://classroom.googleapis.com/v1/courses/$courseid/courseWork/$id?state'); + final patchHead = { + 'Authorization': 'Bearer $_userToken', + 'Content-Type': 'application/json', + }; + + modData['state'] = "PUBLISHED"; + + final patchBody = jsonEncode(modData); + + final patchRep = await ApiService().httpPatch(patchUrl, headers: patchHead, body: patchBody); + + if (patchRep.statusCode != 200) { + print('Request failed with status: ${patchRep.statusCode}.'); + return null; + } + + final patchData = jsonDecode(patchRep.body) as Map; + print('Modify Assignment Response: $patchData'); + return patchData; + + + } + return responseData; } @@ -1222,6 +1269,9 @@ class GoogleLmsService extends LmsInterface { if (courses != null) { for (Course c in courses!) { List parts = await getCourseParticipants(c.id.toString()); + for (Participant p in parts) { + print(p.id); + } var quizzes = (await getQuizzes(c.id, topicId: c.quizTopicId)).where((q) => q.individualStudentsOptions.isNotEmpty); for (Quiz q in quizzes) { for (int p in q.individualStudentsOptions) { @@ -1247,7 +1297,7 @@ class GoogleLmsService extends LmsInterface { return ""; } print("due date: $dueDate"); - var response = await createAssignment(courseId.toString(), "", assignment.name, (assignment.allowsubmissionsfromdate?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch).toString(), (dueDate == null ? assignment.dueDate?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch : dueDate * 1000).toString(), assignment.maxScore.toString(), assignment.description, individualStudentsOptions: [userId!]); + var response = await createAssignment(courseId.toString(), "", assignment.name, (assignment.allowsubmissionsfromdate?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch).toString(), (dueDate == null ? assignment.dueDate?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch : dueDate * 1000).toString(), assignment.maxScore.toString(), assignment.description, individualStudentsOptions: [userId!.toString()]); print(response); return "Created essay override"; } @@ -1260,7 +1310,7 @@ class GoogleLmsService extends LmsInterface { return QuizOverride.empty(); } - await createQuiz(courseId.toString(), assignment.name!, assignment.description ?? "", "", (timeOpen == null ? assignment.timeOpen : DateTime.fromMillisecondsSinceEpoch(timeOpen)).toString(), (timeClose == null ? assignment.timeClose?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch : timeClose * 1000).toString(), individualStudentsOptions: [userId!]); + await createQuiz(courseId.toString(), assignment.name!, assignment.description ?? "", "", (timeOpen == null ? assignment.timeOpen : DateTime.fromMillisecondsSinceEpoch(timeOpen)).toString(), (timeClose == null ? assignment.timeClose?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch : timeClose * 1000).toString(), individualStudentsOptions: [userId!.toString()]); return QuizOverride.empty(); } } diff --git a/LearningLens2025/frontend/lib/Views/g_assignment_create.dart b/LearningLens2025/frontend/lib/Views/g_assignment_create.dart index 34d103de..63af7631 100644 --- a/LearningLens2025/frontend/lib/Views/g_assignment_create.dart +++ b/LearningLens2025/frontend/lib/Views/g_assignment_create.dart @@ -110,7 +110,7 @@ class _CreateAssignmentPageState extends State { return; } - var ess = await GoogleLmsService().createAssignment(_selectedCourseId!, "", _title!, _dueDate?.millisecondsSinceEpoch.toString() ?? "", "", _points?.toString() ?? "", _instructions ?? ""); + var ess = await GoogleLmsService().createAssignment(_selectedCourseId!, "", _title!, "", _dueDate?.millisecondsSinceEpoch.toString() ?? "", _points?.toString() ?? "", _instructions ?? ""); if (ess != null) { print('Assignment created successfully!'); await GoogleLmsService() diff --git a/LearningLens2025/frontend/lib/services/api_service.dart b/LearningLens2025/frontend/lib/services/api_service.dart index 7baba7fc..ca4db31f 100644 --- a/LearningLens2025/frontend/lib/services/api_service.dart +++ b/LearningLens2025/frontend/lib/services/api_service.dart @@ -85,6 +85,34 @@ class ApiService { } } + Future httpPatch( + Uri url, { + Map? headers, + Object? body, + Encoding? encoding, + }) async { + final stopwatch = Stopwatch()..start(); + try { + final response = await http.patch( + url, + headers: headers, + body: body, + encoding: encoding, + ); + stopwatch.stop(); + _handleResponse(response, method: 'PATCH', duration: stopwatch.elapsed); + return response; + } catch (e, stackTrace) { + stopwatch.stop(); + _logger.e( + 'Exception (PATCH) -> $url (${stopwatch.elapsedMilliseconds}ms)', + e, + stackTrace, + ); + rethrow; + } + } + /// Handles the [response], logging success or error messages. /// Includes [method] (GET/POST/...) and [duration] for helpful timing info. void _handleResponse( From af46d814aaeb13b87938f4b6c318713c5943caa6 Mon Sep 17 00:00:00 2001 From: Ryan Appleby Date: Tue, 28 Oct 2025 02:09:19 -0400 Subject: [PATCH 3/6] no idea --- .../lib/Api/lms/google_classroom/google_lms_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart index 28810bf7..27e2d36f 100644 --- a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart +++ b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart @@ -304,7 +304,7 @@ class GoogleLmsService extends LmsInterface { if (studentsJson.containsKey('students')) { for (var student in studentsJson['students']) { participants.add(Participant( - id: int.parse(student['userId']), + id: int.parse(student['profile']['id']), fullname: student['profile']['name']['fullName'], firstname: student['profile']['name']['givenName'], lastname: student['profile']['name']['familyName'], From 863ca90bcb2c4b5e435404816f3ca0f591b49c79 Mon Sep 17 00:00:00 2001 From: Ryan Appleby Date: Sun, 2 Nov 2025 23:21:29 -0500 Subject: [PATCH 4/6] iep classroom --- .../google_classroom/google_lms_service.dart | 292 ++++++++++-------- .../frontend/lib/Api/lms/lms_interface.dart | 44 ++- .../Api/lms/moodle/moodle_lms_service.dart | 46 +-- .../lib/Api/lms/template/api_singleton.dart | 24 +- .../lib/Views/g_assignment_create.dart | 37 ++- .../frontend/lib/Views/iep_page.dart | 31 +- .../lib/Views/view_reflection_page.dart | 4 +- .../frontend/lib/beans/assignment.dart | 3 +- LearningLens2025/frontend/lib/beans/quiz.dart | 4 +- .../frontend/lib/services/api_service.dart | 2 +- 10 files changed, 277 insertions(+), 210 deletions(-) diff --git a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart index 27e2d36f..1367fb9a 100644 --- a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart +++ b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart @@ -72,7 +72,6 @@ class GoogleLmsService extends LmsInterface { List? overrides; late GoogleSignIn _googleSignIn; - // **************************************************************************************** // Auth / Login @@ -304,6 +303,8 @@ class GoogleLmsService extends LmsInterface { if (studentsJson.containsKey('students')) { for (var student in studentsJson['students']) { participants.add(Participant( + // TODO: Int will parse incorrectly. Will need to swap to String or BigInt + // e.g. 123456789012345 would parse to something like 123456789010000 id: int.parse(student['profile']['id']), fullname: student['profile']['name']['fullName'], firstname: student['profile']['name']['givenName'], @@ -380,7 +381,8 @@ class GoogleLmsService extends LmsInterface { @override Future createQuiz(String courseid, String quizname, String quizintro, - String sectionid, String timeopen, String timeclose, {List individualStudentsOptions = const []}) async { + String sectionid, String timeopen, String timeclose, + {List individualStudentsOptions = const []}) async { print('Creating quiz in Google Classroom...'); print('Course ID: $courseid'); print('Quiz Name: $quizname'); @@ -393,8 +395,8 @@ class GoogleLmsService extends LmsInterface { // Convert timeopen to ISO 8601 format String formattedTimeOpen = DateTime.parse(timeopen).toIso8601String(); - String? assignmentId = await createAssignmentHelper( - courseid, quizname, quizintro, sectionid, formattedTimeOpen, individualStudentsOptions); + String? assignmentId = await createAssignmentHelper(courseid, quizname, + quizintro, sectionid, formattedTimeOpen, individualStudentsOptions); if (assignmentId != null) { return int.parse(assignmentId); @@ -408,8 +410,13 @@ class GoogleLmsService extends LmsInterface { } } - Future createAssignmentHelper(String courseId, String title, - String description, String responderUri, String dueDate, List individualStudentsOptions) async { + Future createAssignmentHelper( + String courseId, + String title, + String description, + String responderUri, + String dueDate, + List individualStudentsOptions) async { print('Creating assignment in Google Classroom... Inside helper'); print('Course ID: $courseId'); print('Title: $title'); @@ -453,16 +460,14 @@ class GoogleLmsService extends LmsInterface { "link": {"url": responderUri} } ] - }; - - if (individualStudentsOptions.isNotEmpty) { - requestBody['individualStudentsOptions'] = { - "studentIds" : individualStudentsOptions - }; - } - + if (individualStudentsOptions.isNotEmpty) { + requestBody['individualStudentsOptions'] = { + "studentIds": individualStudentsOptions + }; + } + final body = jsonEncode(requestBody); // Print request details @@ -765,119 +770,70 @@ class GoogleLmsService extends LmsInterface { @override Future?> createAssignment( - String courseid, - String sectionid, - String assignmentName, - String startdate, - String enddate, - String rubricJson, - String description, - {List individualStudentsOptions = const []} - ) async { - final url = Uri.parse( - 'https://classroom.googleapis.com/v1/courses/$courseid/courseWork'); - final headers = { - 'Authorization': 'Bearer $_userToken', - 'Content-Type': 'application/json', - }; - - Map requestBody = { - 'title': assignmentName, - 'description': description, - 'state': individualStudentsOptions.isNotEmpty ? 'DRAFT' : 'PUBLISHED', - 'workType': 'ASSIGNMENT', - 'maxPoints': int.tryParse(rubricJson.toString()), - }; - - DateTime d = DateTime.fromMillisecondsSinceEpoch(int.parse(enddate)); - - if (d != null) { - requestBody['dueDate'] = { - 'year': d.year, - 'month': d.month, - 'day': d.day, - }; - requestBody['dueTime'] = { - 'hours': d.hour, - 'minutes': d.minute, - 'seconds': 0, - }; - } - - String? topicIdNew = await GoogleClassroomApi().getTopicId(courseid, "essay"); - if (topicIdNew != null) { - requestBody['topicId'] = topicIdNew; - } - - print(requestBody); - - final body = jsonEncode(requestBody); - - final response = await ApiService().httpPost(url, headers: headers, body: body); + String courseid, + String sectionid, + String assignmentName, + String startdate, + String enddate, + String rubricJson, + String description, + {List individualStudentsOptions = const []}) async { + final url = Uri.parse( + 'https://classroom.googleapis.com/v1/courses/$courseid/courseWork'); + final headers = { + 'Authorization': 'Bearer $_userToken', + 'Content-Type': 'application/json', + }; - if (response.statusCode != 200) { - print('Request failed with status: ${response.statusCode}.'); - return null; - } + Map requestBody = { + 'title': assignmentName, + 'description': description, + 'state': 'PUBLISHED', + 'workType': 'ASSIGNMENT', + 'maxPoints': int.tryParse(rubricJson.toString()), + }; - final responseData = jsonDecode(response.body) as Map; - print('Create Assignment Response: $responseData'); + DateTime d = DateTime.fromMillisecondsSinceEpoch(int.parse(enddate)); - if (individualStudentsOptions.isNotEmpty) { - var id = responseData['id']; - final modUrl = Uri.parse( - 'https://classroom.googleapis.com/v1/courses/$courseid/courseWork/$id:modifyAssignees'); - final modHead = { - 'Authorization': 'Bearer $_userToken', - 'Content-Type': 'application/json', - }; + requestBody['dueDate'] = { + 'year': d.year, + 'month': d.month, + 'day': d.day, + }; + requestBody['dueTime'] = { + 'hours': d.hour, + 'minutes': d.minute, + 'seconds': 0, + }; - Map modifyAssigneesBody = { - 'assigneeMode': 'INDIVIDUAL_STUDENTS', - 'modifyIndividualStudentsOptions': { - "addStudentIds" : individualStudentsOptions, - "removeStudentIds" : [] - } + if (individualStudentsOptions.isNotEmpty) { + requestBody['assigneeMode'] = 'INDIVIDUAL_STUDENTS'; + requestBody['individualStudentsOptions'] = { + "studentIds": individualStudentsOptions, }; - final modBody = jsonEncode(modifyAssigneesBody); - - final modRep = await ApiService().httpPost(modUrl, headers: modHead, body: modBody); - - if (modRep.statusCode != 200) { - print('Request failed with status: ${modRep.statusCode}.'); - return null; - } - - var modData = jsonDecode(modRep.body) as Map; - print('Modify Assignment Response: $modData'); - - - final patchUrl = Uri.parse( - 'https://classroom.googleapis.com/v1/courses/$courseid/courseWork/$id?state'); - final patchHead = { - 'Authorization': 'Bearer $_userToken', - 'Content-Type': 'application/json', - }; - - modData['state'] = "PUBLISHED"; + } - final patchBody = jsonEncode(modData); + String? topicIdNew = + await GoogleClassroomApi().getTopicId(courseid, "essay"); + if (topicIdNew != null) { + requestBody['topicId'] = topicIdNew; + } - final patchRep = await ApiService().httpPatch(patchUrl, headers: patchHead, body: patchBody); + print(requestBody); - if (patchRep.statusCode != 200) { - print('Request failed with status: ${patchRep.statusCode}.'); - return null; - } - - final patchData = jsonDecode(patchRep.body) as Map; - print('Modify Assignment Response: $patchData'); - return patchData; + final body = jsonEncode(requestBody); + final response = + await ApiService().httpPost(url, headers: headers, body: body); - } + if (response.statusCode != 200) { + print('Request failed with status: ${response.statusCode}.'); + return null; + } - return responseData; + final responseData = jsonDecode(response.body) as Map; + print('Create Assignment Response: $responseData'); + return null; } @override @@ -1262,7 +1218,7 @@ class GoogleLmsService extends LmsInterface { // TODO: implement uploadFileToDraft throw UnimplementedError(); } - + @override Future refreshOverrides() async { List override = []; @@ -1272,16 +1228,42 @@ class GoogleLmsService extends LmsInterface { for (Participant p in parts) { print(p.id); } - var quizzes = (await getQuizzes(c.id, topicId: c.quizTopicId)).where((q) => q.individualStudentsOptions.isNotEmpty); + var quizzes = (await getQuizzes(c.id, topicId: c.quizTopicId)) + .where((q) => q.individualStudentsOptions.isNotEmpty); for (Quiz q in quizzes) { for (int p in q.individualStudentsOptions) { - override.add(Override(override.length, "quiz", q.id!, q.name!, c.id, c.fullName, p, parts.firstWhere((part) => part.id == p).fullname, q.timeClose, null, q.timeClose, 0)); + override.add(Override( + override.length, + "quiz", + q.id!, + q.name!, + c.id, + c.fullName, + p, + parts.firstWhere((part) => part.id == p).fullname, + q.timeClose, + null, + q.timeClose, + 0)); } } - var essays = (await getEssays(c.id, topicId: c.essayTopicId)).where((e) => e.individualStudentsOptions.isNotEmpty); + var essays = (await getEssays(c.id, topicId: c.essayTopicId)) + .where((e) => e.individualStudentsOptions.isNotEmpty); for (Assignment e in essays) { for (int p in e.individualStudentsOptions) { - override.add(Override(override.length, "essay", e.id, e.name, c.id, c.fullName, p, parts.firstWhere((part) => part.id == p).fullname, e.dueDate, null, e.cutoffDate, 0)); + override.add(Override( + override.length, + "essay", + e.id, + e.name, + c.id, + c.fullName, + p, + parts.firstWhere((part) => part.id == p).fullname, + e.dueDate, + null, + e.cutoffDate, + 0)); } } } @@ -1289,28 +1271,80 @@ class GoogleLmsService extends LmsInterface { } @override - Future addEssayOverride({required int assignid, int? courseId, int? userId, int? groupId, int? allowsubmissionsfromdate, int? dueDate, int? cutoffDate, int? timelimit, int? sortorder}) async { - if (courseId == null) { return "";} - var assignment = getCourse(courseId).essays?.firstWhereOrNull((e) => e.id == assignid); + Future addEssayOverride( + {required int assignid, + int? courseId, + int? userId, + int? groupId, + int? allowsubmissionsfromdate, + int? dueDate, + int? cutoffDate, + int? timelimit, + int? sortorder}) async { + if (courseId == null) { + return ""; + } + var assignment = + getCourse(courseId).essays?.firstWhereOrNull((e) => e.id == assignid); if (assignment == null) { print(getCourse(courseId).essays); return ""; } print("due date: $dueDate"); - var response = await createAssignment(courseId.toString(), "", assignment.name, (assignment.allowsubmissionsfromdate?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch).toString(), (dueDate == null ? assignment.dueDate?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch : dueDate * 1000).toString(), assignment.maxScore.toString(), assignment.description, individualStudentsOptions: [userId!.toString()]); + var response = await createAssignment( + courseId.toString(), + "", + assignment.name, + (assignment.allowsubmissionsfromdate?.millisecondsSinceEpoch ?? + DateTime.now().millisecondsSinceEpoch) + .toString(), + (dueDate == null + ? assignment.dueDate?.millisecondsSinceEpoch ?? + DateTime.now().millisecondsSinceEpoch + : dueDate * 1000) + .toString(), + assignment.maxScore.toString(), + assignment.description, + individualStudentsOptions: [userId!]); print(response); return "Created essay override"; } @override - Future addQuizOverride({required int quizId, int? courseId, int? userId, int? groupId, int? timeOpen, int? timeClose, int? timeLimit, int? attempts, String? password}) async { - if (courseId == null) { return QuizOverride.empty();} - var assignment = getCourse(courseId).quizzes?.firstWhereOrNull((e) => e.id == quizId); + Future addQuizOverride( + {required int quizId, + int? courseId, + int? userId, + int? groupId, + int? timeOpen, + int? timeClose, + int? timeLimit, + int? attempts, + String? password}) async { + if (courseId == null) { + return QuizOverride.empty(); + } + var assignment = + getCourse(courseId).quizzes?.firstWhereOrNull((e) => e.id == quizId); if (assignment == null) { return QuizOverride.empty(); } - await createQuiz(courseId.toString(), assignment.name!, assignment.description ?? "", "", (timeOpen == null ? assignment.timeOpen : DateTime.fromMillisecondsSinceEpoch(timeOpen)).toString(), (timeClose == null ? assignment.timeClose?.millisecondsSinceEpoch ?? DateTime.now().millisecondsSinceEpoch : timeClose * 1000).toString(), individualStudentsOptions: [userId!.toString()]); + await createQuiz( + courseId.toString(), + assignment.name!, + assignment.description ?? "", + "", + (timeOpen == null + ? assignment.timeOpen + : DateTime.fromMillisecondsSinceEpoch(timeOpen)) + .toString(), + (timeClose == null + ? assignment.timeClose?.millisecondsSinceEpoch ?? + DateTime.now().millisecondsSinceEpoch + : timeClose * 1000) + .toString(), + individualStudentsOptions: [userId!]); return QuizOverride.empty(); } } diff --git a/LearningLens2025/frontend/lib/Api/lms/lms_interface.dart b/LearningLens2025/frontend/lib/Api/lms/lms_interface.dart index 95ae67fb..7556514f 100644 --- a/LearningLens2025/frontend/lib/Api/lms/lms_interface.dart +++ b/LearningLens2025/frontend/lib/Api/lms/lms_interface.dart @@ -120,27 +120,25 @@ abstract class LmsInterface { throw UnimplementedError(); } - Future addQuizOverride({ - required int quizId, - int? userId, - int? groupId, - int? timeOpen, - int? timeClose, - int? timeLimit, - int? attempts, - String? password, - int? courseId - }); - - Future addEssayOverride({ - required int assignid, - int? userId, - int? groupId, - int? allowsubmissionsfromdate, - int? dueDate, - int? cutoffDate, - int? timelimit, - int? sortorder, - int? courseId - }); + Future addQuizOverride( + {required int quizId, + int? userId, + int? groupId, + int? timeOpen, + int? timeClose, + int? timeLimit, + int? attempts, + String? password, + int? courseId}); + + Future addEssayOverride( + {required int assignid, + int? userId, + int? groupId, + int? allowsubmissionsfromdate, + int? dueDate, + int? cutoffDate, + int? timelimit, + int? sortorder, + int? courseId}); } diff --git a/LearningLens2025/frontend/lib/Api/lms/moodle/moodle_lms_service.dart b/LearningLens2025/frontend/lib/Api/lms/moodle/moodle_lms_service.dart index 150b28e0..1c5d1bc0 100644 --- a/LearningLens2025/frontend/lib/Api/lms/moodle/moodle_lms_service.dart +++ b/LearningLens2025/frontend/lib/Api/lms/moodle/moodle_lms_service.dart @@ -64,7 +64,6 @@ class MoodleLmsService implements LmsInterface { int? userId; - @override List? overrides; @@ -134,6 +133,7 @@ class MoodleLmsService implements LmsInterface { return _userToken != null; } + @override Future refreshOverrides() async { List>> futures = [ _getQuizOverrides(), @@ -1012,17 +1012,17 @@ class MoodleLmsService implements LmsInterface { } } - Future addQuizOverride({ - required int quizId, - int? userId, - int? groupId, - int? timeOpen, - int? timeClose, - int? timeLimit, - int? attempts, - String? password, - int? courseId - }) async { + @override + Future addQuizOverride( + {required int quizId, + int? userId, + int? groupId, + int? timeOpen, + int? timeClose, + int? timeLimit, + int? attempts, + String? password, + int? courseId}) async { if (_userToken == null) throw StateError('User not logged in to Moodle'); final url = Uri.parse('$apiURL$serverUrl'); @@ -1055,17 +1055,17 @@ class MoodleLmsService implements LmsInterface { } } - Future addEssayOverride({ - required int assignid, - int? userId, - int? groupId, - int? allowsubmissionsfromdate, - int? dueDate, - int? cutoffDate, - int? timelimit, - int? sortorder, - int? courseId - }) async { + @override + Future addEssayOverride( + {required int assignid, + int? userId, + int? groupId, + int? allowsubmissionsfromdate, + int? dueDate, + int? cutoffDate, + int? timelimit, + int? sortorder, + int? courseId}) async { if (_userToken == null) throw StateError('User not logged in to Moodle'); final url = Uri.parse('$apiURL$serverUrl'); diff --git a/LearningLens2025/frontend/lib/Api/lms/template/api_singleton.dart b/LearningLens2025/frontend/lib/Api/lms/template/api_singleton.dart index 77516cc7..5baabe81 100644 --- a/LearningLens2025/frontend/lib/Api/lms/template/api_singleton.dart +++ b/LearningLens2025/frontend/lib/Api/lms/template/api_singleton.dart @@ -312,7 +312,7 @@ class ApiSingleton implements LmsInterface { // TODO: implement getSubmissionStatusRaw throw UnimplementedError(); } - + @override Future refreshOverrides() { // TODO: implement refreshOverrides @@ -320,13 +320,31 @@ class ApiSingleton implements LmsInterface { } @override - Future addEssayOverride({required int assignid, int? userId, int? groupId, int? allowsubmissionsfromdate, int? dueDate, int? cutoffDate, int? timelimit, int? sortorder, int? courseId}) { + Future addEssayOverride( + {required int assignid, + int? userId, + int? groupId, + int? allowsubmissionsfromdate, + int? dueDate, + int? cutoffDate, + int? timelimit, + int? sortorder, + int? courseId}) { // TODO: implement addEssayOverride throw UnimplementedError(); } @override - Future addQuizOverride({required int quizId, int? userId, int? groupId, int? timeOpen, int? timeClose, int? timeLimit, int? attempts, String? password, int? courseId}) { + Future addQuizOverride( + {required int quizId, + int? userId, + int? groupId, + int? timeOpen, + int? timeClose, + int? timeLimit, + int? attempts, + String? password, + int? courseId}) { // TODO: implement addQuizOverride throw UnimplementedError(); } diff --git a/LearningLens2025/frontend/lib/Views/g_assignment_create.dart b/LearningLens2025/frontend/lib/Views/g_assignment_create.dart index 63af7631..0aef9d64 100644 --- a/LearningLens2025/frontend/lib/Views/g_assignment_create.dart +++ b/LearningLens2025/frontend/lib/Views/g_assignment_create.dart @@ -110,25 +110,30 @@ class _CreateAssignmentPageState extends State { return; } - var ess = await GoogleLmsService().createAssignment(_selectedCourseId!, "", _title!, "", _dueDate?.millisecondsSinceEpoch.toString() ?? "", _points?.toString() ?? "", _instructions ?? ""); + var ess = await GoogleLmsService().createAssignment( + _selectedCourseId!, + "", + _title!, + "", + _dueDate?.millisecondsSinceEpoch.toString() ?? "", + _points?.toString() ?? "", + _instructions ?? ""); if (ess != null) { - print('Assignment created successfully!'); - await GoogleLmsService() - .courses - ?.firstWhere((c) => c.id.toString() == _selectedCourseId) - .refreshEssays(); - Navigator.pop(context); - } - else { + print('Assignment created successfully!'); + await GoogleLmsService() + .courses + ?.firstWhere((c) => c.id.toString() == _selectedCourseId) + .refreshEssays(); + Navigator.pop(context); + } else { ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text('Error creating assignment.'), - backgroundColor: Colors.red, - ), - ); + SnackBar( + content: Text('Error creating assignment.'), + backgroundColor: Colors.red, + ), + ); } -setState(() => _isSubmitting = false); + setState(() => _isSubmitting = false); } } diff --git a/LearningLens2025/frontend/lib/Views/iep_page.dart b/LearningLens2025/frontend/lib/Views/iep_page.dart index da44d971..70772a53 100644 --- a/LearningLens2025/frontend/lib/Views/iep_page.dart +++ b/LearningLens2025/frontend/lib/Views/iep_page.dart @@ -11,7 +11,6 @@ import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart'; import 'package:learninglens_app/Api/llm/openai_api.dart'; import 'package:learninglens_app/Api/llm/perplexity_api.dart'; import "package:learninglens_app/Api/lms/factory/lms_factory.dart"; -import "package:learninglens_app/Api/lms/moodle/moodle_lms_service.dart"; import "package:learninglens_app/Controller/custom_appbar.dart"; import 'package:learninglens_app/Controller/html_converter.dart'; import 'package:learninglens_app/beans/assessment.dart'; @@ -563,12 +562,20 @@ class _IepPageState extends State { epochTime2 != null) ? () async { if (selectedAssignment?.type == 'quiz') { - await quizOver(epochTime!, int.parse(selectedCourse!), selectedAssignment!.id, - userId!, attempts!); + await quizOver( + epochTime!, + int.parse(selectedCourse!), + selectedAssignment!.id, + userId!, + attempts!); } else if (selectedAssignment?.type == 'essay') { - await essayOver(epochTime!, int.parse(selectedCourse!), selectedAssignment!.id, - userId!, epochTime2!); + await essayOver( + epochTime!, + int.parse(selectedCourse!), + selectedAssignment!.id, + userId!, + epochTime2!); } resetForm(false); } @@ -703,7 +710,8 @@ class _IepPageState extends State { Future>? getAllParticipants(String courseID) async { List? participants; - participants = await LmsFactory.getLmsService().getCourseParticipants(courseID); + participants = + await LmsFactory.getLmsService().getCourseParticipants(courseID); return participants; } @@ -723,7 +731,8 @@ class _IepPageState extends State { Future> handleAssessmentSelection(int? courseID) async { if (courseID != null) { - List essayList = await LmsFactory.getLmsService().getEssays(courseID); + List essayList = + await LmsFactory.getLmsService().getEssays(courseID); // Fetch quizzes (if available). List quizList = []; try { @@ -760,7 +769,8 @@ class _IepPageState extends State { }); } - Future quizOver(int epochTime, int courseId, int quizId, int userId, int attempts) async { + Future quizOver( + int epochTime, int courseId, int quizId, int userId, int attempts) async { await LmsFactory.getLmsService().addQuizOverride( quizId: quizId, courseId: courseId, @@ -769,7 +779,7 @@ class _IepPageState extends State { attempts: attempts); await LmsFactory.getLmsService().refreshOverrides(); setState(() { - overrides = LmsFactory.getLmsService().overrides; + overrides = LmsFactory.getLmsService().overrides; overrides?.sort((a, b) => a.fullname.compareTo(b.fullname)); }); ScaffoldMessenger.of(context).showSnackBar( @@ -777,7 +787,8 @@ class _IepPageState extends State { ); } - Future essayOver(int epochTime, int courseId, int essayId, int userId, int epochTime2) async { + Future essayOver(int epochTime, int courseId, int essayId, int userId, + int epochTime2) async { await LmsFactory.getLmsService().addEssayOverride( assignid: essayId, courseId: courseId, diff --git a/LearningLens2025/frontend/lib/Views/view_reflection_page.dart b/LearningLens2025/frontend/lib/Views/view_reflection_page.dart index 30233e82..ca200d82 100644 --- a/LearningLens2025/frontend/lib/Views/view_reflection_page.dart +++ b/LearningLens2025/frontend/lib/Views/view_reflection_page.dart @@ -7,10 +7,10 @@ class ViewReflectionPage extends StatelessWidget { final Submission submission; const ViewReflectionPage({ - Key? key, + super.key, required this.participant, required this.submission, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/LearningLens2025/frontend/lib/beans/assignment.dart b/LearningLens2025/frontend/lib/beans/assignment.dart index 5ec59edb..f763de31 100644 --- a/LearningLens2025/frontend/lib/beans/assignment.dart +++ b/LearningLens2025/frontend/lib/beans/assignment.dart @@ -92,7 +92,8 @@ class Assignment implements LearningLensInterface { ); if (json['AssigneeMode']?.toString() == "INDIVIDUAL_STUDENTS") { final studentOptions = json["studentIds"] as List; - a.individualStudentsOptions.addAll(studentOptions.map((e) => int.parse(e.toString()))); + a.individualStudentsOptions + .addAll(studentOptions.map((e) => int.parse(e.toString()))); } a.maxScore = json["maxPoints"]; return a; diff --git a/LearningLens2025/frontend/lib/beans/quiz.dart b/LearningLens2025/frontend/lib/beans/quiz.dart index 9776d1c6..92aff8a1 100644 --- a/LearningLens2025/frontend/lib/beans/quiz.dart +++ b/LearningLens2025/frontend/lib/beans/quiz.dart @@ -1,4 +1,3 @@ -import 'package:learninglens_app/Controller/g_bean.dart'; import 'package:xml/xml.dart'; import 'package:learninglens_app/beans/question.dart'; import 'package:learninglens_app/beans/xml_consts.dart'; @@ -105,7 +104,8 @@ class Quiz { print('Debug: Quiz object created successfully'); if (json['AssigneeMode']?.toString() == "INDIVIDUAL_STUDENTS") { final studentOptions = json["studentIds"] as List; - tmpQuiz.individualStudentsOptions.addAll(studentOptions.map((e) => int.parse(e.toString()))); + tmpQuiz.individualStudentsOptions + .addAll(studentOptions.map((e) => int.parse(e.toString()))); } return tmpQuiz; } diff --git a/LearningLens2025/frontend/lib/services/api_service.dart b/LearningLens2025/frontend/lib/services/api_service.dart index ca4db31f..c356f5ea 100644 --- a/LearningLens2025/frontend/lib/services/api_service.dart +++ b/LearningLens2025/frontend/lib/services/api_service.dart @@ -85,7 +85,7 @@ class ApiService { } } - Future httpPatch( + Future httpPatch( Uri url, { Map? headers, Object? body, From b74677838a5add1efab829380a5571f8c39b3da0 Mon Sep 17 00:00:00 2001 From: Ryan Appleby Date: Sun, 2 Nov 2025 23:31:34 -0500 Subject: [PATCH 5/6] format and fix --- .../google_classroom/google_lms_service.dart | 17 ++++++++--------- .../frontend/lib/Views/g_assignment_create.dart | 3 --- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart index 8e54c0a9..03e0b079 100644 --- a/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart +++ b/LearningLens2025/frontend/lib/Api/lms/google_classroom/google_lms_service.dart @@ -899,15 +899,14 @@ class GoogleLmsService extends LmsInterface { @override // TODO - google API doesn't have an endpoint to pass in the rubric like Moodle Future?> createAssignment( - String courseid, - String sectionid, - String assignmentName, - String startdate, - String enddate, - String rubricJson, - String description, - {List individualStudentsOptions = const []} - ) async { + String courseid, + String sectionid, + String assignmentName, + String startdate, + String enddate, + String rubricJson, + String description, + {List individualStudentsOptions = const []}) async { print('Creating assignment...'); print('Course ID: $courseid'); print('Section ID: $sectionid'); diff --git a/LearningLens2025/frontend/lib/Views/g_assignment_create.dart b/LearningLens2025/frontend/lib/Views/g_assignment_create.dart index 0aef9d64..342476b3 100644 --- a/LearningLens2025/frontend/lib/Views/g_assignment_create.dart +++ b/LearningLens2025/frontend/lib/Views/g_assignment_create.dart @@ -4,14 +4,11 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:learninglens_app/Api/lms/factory/lms_factory.dart'; -import 'package:learninglens_app/Api/lms/google_classroom/google_classroom_api.dart'; import 'package:learninglens_app/Api/lms/google_classroom/google_lms_service.dart'; import 'package:learninglens_app/Controller/custom_appbar.dart'; import 'package:learninglens_app/services/local_storage_service.dart'; class CreateAssignmentPage extends StatefulWidget { - final GoogleClassroomApi _googleClassroomApi = GoogleClassroomApi(); - @override _CreateAssignmentPageState createState() => _CreateAssignmentPageState(); } From c826130643833ec776d761c9551d0d25848d0daf Mon Sep 17 00:00:00 2001 From: Ryan Appleby Date: Sun, 2 Nov 2025 23:32:40 -0500 Subject: [PATCH 6/6] remove unused --- .../frontend/lib/services/api_service.dart | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/LearningLens2025/frontend/lib/services/api_service.dart b/LearningLens2025/frontend/lib/services/api_service.dart index c356f5ea..7baba7fc 100644 --- a/LearningLens2025/frontend/lib/services/api_service.dart +++ b/LearningLens2025/frontend/lib/services/api_service.dart @@ -85,34 +85,6 @@ class ApiService { } } - Future httpPatch( - Uri url, { - Map? headers, - Object? body, - Encoding? encoding, - }) async { - final stopwatch = Stopwatch()..start(); - try { - final response = await http.patch( - url, - headers: headers, - body: body, - encoding: encoding, - ); - stopwatch.stop(); - _handleResponse(response, method: 'PATCH', duration: stopwatch.elapsed); - return response; - } catch (e, stackTrace) { - stopwatch.stop(); - _logger.e( - 'Exception (PATCH) -> $url (${stopwatch.elapsedMilliseconds}ms)', - e, - stackTrace, - ); - rethrow; - } - } - /// Handles the [response], logging success or error messages. /// Includes [method] (GET/POST/...) and [duration] for helpful timing info. void _handleResponse(