Skip to content

Commit

Permalink
Add grades section to course detail (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
thermitex authored Jun 16, 2024
1 parent 0c7d726 commit e645b0d
Show file tree
Hide file tree
Showing 18 changed files with 518 additions and 70 deletions.
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<data android:mimeType="text/plain"/>
</intent>
</queries>
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Permissions for local notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
Expand Down
9 changes: 9 additions & 0 deletions jsons/moodleCourseGrade.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"itemname": "",
"weight?": "",
"grade": "",
"range": "",
"feedback?": "",
"percentage?": "",
"contributiontocoursetotal?": ""
}
23 changes: 23 additions & 0 deletions lib/src/common/services/constants.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:flutter/material.dart';

/// Constants used in Cuckoo.
class Constants {
static const kEventsTitle = 'Events';
Expand Down Expand Up @@ -64,6 +66,22 @@ class Constants {
'Reminder will apply to events that does not contain the content, case-insensitive.';
static const kRuleActionMatchedDesc =
'Reminder will apply to events that matches the content as a regular expression pattern.';
static const kReminderSubjectChoices = [
'Course Code',
'Course Name',
'Event Title',
];
static const kReminderSubjectChoiceIcons = [
Icons.data_array_rounded,
Icons.school_rounded,
Icons.event_rounded
];
static const kReminderActionChoices = [
'Contains',
'Does Not Contain',
'Matches'
];
static const kReminderRelationChoices = ['AND', 'OR'];
static const kReminderSavedPrompt = 'Reminder Saved';
static const kReminderDeletedPrompt = 'Reminder Deleted';
static const kCustomEventDeletedPrompt = 'Custom Event Deleted';
Expand All @@ -84,6 +102,11 @@ class Constants {
static const kShowAllCoursesButton = 'Show All Courses';
static const kSetCourseFavorite = 'Course Marked As Favorite';
static const kUnsetCourseFavorite = 'Course Unmarked As Favorite';
static const kCourseViewTypeDisplayTexts = ['Course Contents', 'Grades'];
static const kCourseViewTypeIcons = [
Icons.format_list_bulleted_rounded,
Icons.assessment_outlined,
];
static const kDownloadFileLoading = 'Downloading file...';
static const kEventsClearPrompt =
'Amazing! There are currently no upcoming events for you.';
Expand Down
30 changes: 30 additions & 0 deletions lib/src/common/services/moodle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:cuckoo/src/models/index.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:html/parser.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:table_calendar/table_calendar.dart';
Expand Down Expand Up @@ -335,6 +336,35 @@ class Moodle {
return content;
}

/// Get grades of a course by accessing the grades table.
///
/// Note that the hierarchy of the grades table is not preserved here.
/// Only returns a plain lists of valid grade items.
static Future<List<MoodleCourseGrade>?> getCourseGrades(
MoodleCourse course) async {
final response =
await callFunction(MoodleFunctions.getGradesTable, params: {
'courseid': course.id,
'userid': Moodle()._userId,
});
if (response.fail || response.data == null) return null;

// Convert data to grades
final grades = <MoodleCourseGrade>[];
final data = response.data as Map;
final gradeItems = data['tables'].first['tabledata'] as List?;
if (gradeItems != null) {
for (final gradeItem in gradeItems) {
try {
final grade = MoodleCourseGrade.fromJson(gradeItem);
grades.add(grade);
} catch (_) {}
}
}

return grades;
}

/// Download a file associated with the course module.
///
/// Returns the downloaded path if the download is successful and returns null
Expand Down
45 changes: 43 additions & 2 deletions lib/src/common/services/moodle_extra.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class MoodleFunctions {
'core_completion_update_activity_completion_status_manually';
static const getCourseContents = 'core_course_get_contents';
static const recordCourseView = 'core_course_view_course';
static const getGradesTable = 'gradereport_user_get_grades_table';
}

/// Types of Moodle events.
Expand Down Expand Up @@ -143,8 +144,7 @@ extension MoodleEventExtension on MoodleEvent {
completed = c;
Moodle().eventManager._notifyManually(flushCache: true);
Moodle()._saveEvents();
bool shouldSync =
Settings().get<bool>(SettingsKey.syncCompletionStatus) ?? true;
bool shouldSync = trueSettingsValue(SettingsKey.syncCompletionStatus);
if (shouldSync) Moodle.syncEventCompletion();
}

Expand Down Expand Up @@ -214,3 +214,44 @@ extension MoodleCourseModuleExtension on MoodleCourseModule {
contentsinfo != null &&
modname == 'resource';
}

/// Shortcuts for Moodle Course Grades
extension MoodleCourseGradeExtension on MoodleCourseGrade {
/// Get the double value of the grade.
double? get gradeValue => double.tryParse(grade);

/// Get the grade string.
String get gradeStr => gradeValue?.toStringAsFixed(1) ?? grade;

/// Get both ends of the range.
List<int>? get rangeEnds {
final comps = range.split('&ndash;');
if (comps.length == 2) {
final ends = comps.map((c) => int.tryParse(c)).toList();
if (ends.first != null && ends.last != null) {
return [ends.first!, ends.last!];
}
}
return null;
}

/// Parse the title of the grade.
String get title {
final document = parse(itemname);
final titles = document.getElementsByClassName('gradeitemheader');
if (titles.isNotEmpty) {
return titles.first.text;
}
return 'Unkown Grade Item';
}

/// Get url of the grade.
String? get itemUrl {
final document = parse(itemname);
final links = document.getElementsByTagName('a');
if (links.isNotEmpty) {
return links.first.attributes['href'];
}
return null;
}
}
2 changes: 1 addition & 1 deletion lib/src/common/services/moodle_managers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class MoodleCourseManager with ChangeNotifier {

// ---------------------Context Watch Interfaces Start---------------------
// ONLY use the methods below when you are interacting with the manager
// outside `moodle.dart` using `context.eventManager`.
// outside `moodle.dart` using `context.courseManager`.

/// Enrolled courses of current Moodle user.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import 'package:cuckoo/src/common/extensions/extensions.dart';
import 'package:cuckoo/src/common/ui/ui.dart';
import 'package:flutter/material.dart';

class InputSelectorAccessory extends StatelessWidget {
const InputSelectorAccessory(this.description,
class CuckooSelector extends StatelessWidget {
const CuckooSelector(this.description,
{super.key,
this.icon,
this.onPressed,
this.color,
this.backgroundColor,
this.fontSize = 12.0,
this.borderRadius = 10.0});
Expand All @@ -17,6 +18,9 @@ class InputSelectorAccessory extends StatelessWidget {
/// Background color of the selector.
final Color? backgroundColor;

/// Color of the display.
final Color? color;

/// Size of the font.
final double fontSize;

Expand All @@ -36,7 +40,7 @@ class InputSelectorAccessory extends StatelessWidget {
children
..add(Icon(
icon,
color: ColorPresets.primary,
color: color ?? ColorPresets.primary,
size: 16.0,
))
..add(const SizedBox(width: 4.0));
Expand All @@ -48,13 +52,13 @@ class InputSelectorAccessory extends StatelessWidget {
child: Text(
description,
style: TextStylePresets.body(size: fontSize, weight: FontWeight.w500)
.copyWith(color: ColorPresets.primary),
.copyWith(color: color ?? ColorPresets.primary),
),
),
const SizedBox(width: 2.0),
const Icon(
Icon(
Icons.expand_more_rounded,
color: ColorPresets.primary,
color: color ?? ColorPresets.primary,
size: 15.0,
)
]);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/common/ui/ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export '../widgets/error_panel.dart';
export 'toast.dart';
export 'form.dart';
export 'form_field.dart';
export 'input_selector.dart';
export 'selector.dart';
export 'selection_panel.dart';
export 'date_picker.dart';
export 'dialog.dart';
Expand Down
1 change: 1 addition & 0 deletions lib/src/models/index.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'moodleCourseGrade.dart';
export 'moodleAutoLoginInfo.dart';
export 'moodleSiteInfo.dart';
export 'moodleEvent.dart';
Expand Down
20 changes: 20 additions & 0 deletions lib/src/models/moodleCourseGrade.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:json_annotation/json_annotation.dart';

part 'moodleCourseGrade.g.dart';

@JsonSerializable()
class MoodleCourseGrade {
MoodleCourseGrade();

late String itemname;
String? weight;
late String grade;
late String range;
String? feedback;
String? percentage;
String? contributiontocoursetotal;

factory MoodleCourseGrade.fromJson(Map<String, dynamic> json) =>
_$MoodleCourseGradeFromJson(json);
Map<String, dynamic> toJson() => _$MoodleCourseGradeToJson(this);
}
29 changes: 29 additions & 0 deletions lib/src/models/moodleCourseGrade.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e645b0d

Please sign in to comment.