Skip to content

Commit

Permalink
Working Assignment Groups
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulon12 committed Dec 26, 2024
1 parent 19d82ad commit 1d43a3d
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 32 deletions.
8 changes: 8 additions & 0 deletions CanvasPlusPlayground.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,14 @@
B7AD550C2CD4257B00FB09BB /* IntelligenceOnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AD550B2CD4257400FB09BB /* IntelligenceOnboardingView.swift */; };
B7D95D772D07C3D3002AD955 /* ICSParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7D95D762D07C3D3002AD955 /* ICSParser.swift */; };
B7D95DB72D0A8D78002AD955 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7D95DB62D0A8D75002AD955 /* SettingsView.swift */; };
B7E59A0B2D1CFF8C001836FE /* GetAssignmentGroupsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7E59A0A2D1CFF8C001836FE /* GetAssignmentGroupsRequest.swift */; };
B7F950322D118EAF004BB470 /* String+StripHTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F950312D118EAA004BB470 /* String+StripHTML.swift */; };
B7F950342D11A00F004BB470 /* StatusToolbarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F950332D11A00F004BB470 /* StatusToolbarItem.swift */; };
B7F950372D127869004BB470 /* ProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F950362D127861004BB470 /* ProfileManager.swift */; };
B7F950392D1279A1004BB470 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F950382D1279A1004BB470 /* User.swift */; };
B7F9503B2D127AD0004BB470 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F9503A2D127AD0004BB470 /* Profile.swift */; };
B7F9503D2D12ADAE004BB470 /* Submission.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F9503C2D12ADAE004BB470 /* Submission.swift */; };
B7F9503F2D133435004BB470 /* AssignmentGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F9503E2D133435004BB470 /* AssignmentGroup.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -188,12 +190,14 @@
B7AD550B2CD4257400FB09BB /* IntelligenceOnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntelligenceOnboardingView.swift; sourceTree = "<group>"; };
B7D95D762D07C3D3002AD955 /* ICSParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ICSParser.swift; sourceTree = "<group>"; };
B7D95DB62D0A8D75002AD955 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
B7E59A0A2D1CFF8C001836FE /* GetAssignmentGroupsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetAssignmentGroupsRequest.swift; sourceTree = "<group>"; };
B7F950312D118EAA004BB470 /* String+StripHTML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+StripHTML.swift"; sourceTree = "<group>"; };
B7F950332D11A00F004BB470 /* StatusToolbarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusToolbarItem.swift; sourceTree = "<group>"; };
B7F950362D127861004BB470 /* ProfileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileManager.swift; sourceTree = "<group>"; };
B7F950382D1279A1004BB470 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
B7F9503A2D127AD0004BB470 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; };
B7F9503C2D12ADAE004BB470 /* Submission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Submission.swift; sourceTree = "<group>"; };
B7F9503E2D133435004BB470 /* AssignmentGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssignmentGroup.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -250,6 +254,7 @@
A3049B7E2D160C03002F3166 /* GetTabsRequest.swift */,
A3049B802D160F6B002F3166 /* GetAnnouncementsRequest.swift */,
A3049B822D161432002F3166 /* GetAssignmentsRequest.swift */,
B7E59A0A2D1CFF8C001836FE /* GetAssignmentGroupsRequest.swift */,
A3049B842D161455002F3166 /* GetEnrollmentsRequest.swift */,
A3049B862D16146D002F3166 /* GetQuizzesRequest.swift */,
A3D1614E2D1784E3004055FB /* GetUserRequest.swift */,
Expand Down Expand Up @@ -446,6 +451,7 @@
A324BA602D079927005F53FA /* Plain */ = {
isa = PBXGroup;
children = (
B7F9503E2D133435004BB470 /* AssignmentGroup.swift */,
B7F9503A2D127AD0004BB470 /* Profile.swift */,
B7F950382D1279A1004BB470 /* User.swift */,
192EC0492C963B9000AF8528 /* Assignment.swift */,
Expand Down Expand Up @@ -630,6 +636,7 @@
192EC0482C963ACB00AF8528 /* CourseAssignmentManager.swift in Sources */,
B7D95D772D07C3D3002AD955 /* ICSParser.swift in Sources */,
B7F9503B2D127AD0004BB470 /* Profile.swift in Sources */,
B7E59A0B2D1CFF8C001836FE /* GetAssignmentGroupsRequest.swift in Sources */,
B76455012C8DF61B002DF00E /* StorageKeys.swift in Sources */,
A3FFD03A2CDEC2AC006BAB51 /* LookupCondition.swift in Sources */,
B7D95DB72D0A8D78002AD955 /* SettingsView.swift in Sources */,
Expand All @@ -644,6 +651,7 @@
A3FFD03E2CE0065A006BAB51 /* NetworkError.swift in Sources */,
B53D95A22CA0A22A00647EE9 /* PeopleView.swift in Sources */,
A3049B752D15DC1C002F3166 /* GetCoursesRequest.swift in Sources */,
B7F9503F2D133435004BB470 /* AssignmentGroup.swift in Sources */,
B76455042C8DF61B002DF00E /* CourseView.swift in Sources */,
A3049B682D0F3ECA002F3166 /* QuizzesViewModel.swift in Sources */,
9B92A4252C93856100C21CFC /* CourseAnnouncementManager.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// GetAssignmentGroupsRequest.swift
// CanvasPlusPlayground
//
// Created by Rahul on 12/26/24.
//

import Foundation

struct GetAssignmentGroupsRequest: ArrayAPIRequest {
typealias Subject = AssignmentGroup

let courseId: String

// Path for the request
var path: String { "courses/\(courseId)/assignment_groups" }

// Query parameters
var queryParameters: [QueryParameter] {
[
("override_assignment_dates", overrideAssignmentDates),
("grading_period_id", gradingPeriodId),
("scope_assignments_to_student", scopeAssignmentsToStudent)
]
+ include.map { ("include[]", $0) }
+ assignmentIds.map { ("assignment_ids[]", $0) }
+ excludeAssignmentSubmissionTypes.map { ("exclude_assignment_submission_types[]", $0) }
}

// MARK: Query Params
let include: [String]
let assignmentIds: [String]
let excludeAssignmentSubmissionTypes: [String]
let overrideAssignmentDates: Bool?
let gradingPeriodId: Int?
let scopeAssignmentsToStudent: Bool?

// Initializer
init(
courseId: String,
include: [String] = [],
assignmentIds: [String] = [],
excludeAssignmentSubmissionTypes: [String] = [],
overrideAssignmentDates: Bool? = nil,
gradingPeriodId: Int? = nil,
scopeAssignmentsToStudent: Bool? = nil
) {
self.courseId = courseId
self.include = include
self.assignmentIds = assignmentIds
self.excludeAssignmentSubmissionTypes = excludeAssignmentSubmissionTypes
self.overrideAssignmentDates = overrideAssignmentDates
self.gradingPeriodId = gradingPeriodId
self.scopeAssignmentsToStudent = scopeAssignmentsToStudent
}

/* MARK: Request Caching (Optional Implementation)
var requestId: Int? { courseId.asInt }
var requestIdKey: ParentKeyPath<AssignmentGroup, Int?> { .createReadable(\.courseId) }
var idPredicate: Predicate<AssignmentGroup> {
#Predicate<AssignmentGroup> { group in
group.courseId == requestId
}
}

var customPredicate: Predicate<AssignmentGroup> {
let ids = assignmentIds.compactMap(\.?.asInt)
let assignmentIdsPred = assignmentIds.isEmpty ? .true : #Predicate<AssignmentGroup> { group in
ids.contains(group.id)
}
return assignmentIdsPred
}
*/
}
6 changes: 5 additions & 1 deletion CanvasPlusPlayground/Common/Network/CanvasRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ struct CanvasRequest {
static func getAssignments(courseId: String) -> GetAssignmentsRequest {
GetAssignmentsRequest(courseId: courseId)
}


static func getAssignmentGroups(courseId: String, include includeItems: [String] = ["assignments"]) -> GetAssignmentGroupsRequest {
GetAssignmentGroupsRequest(courseId: courseId, include: includeItems)
}

static func getEnrollments(courseId: String, userId: Int? = nil, perPage: Int = 50) -> GetEnrollmentsRequest {
GetEnrollmentsRequest(courseId: courseId, userId: userId?.asString, perPage: perPage)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,39 @@ import SwiftUI
@Observable
class CourseAssignmentManager {
private let courseID: String?
var assignments = [Assignment]()
var assignmentGroups: [AssignmentGroup] = []

init(courseID: String?) {
self.courseID = courseID
}

func fetchAssignments() async {
guard let courseID = courseID, let (data, _) = try? await CanvasService.shared.fetchResponse(CanvasRequest.getAssignments(courseId: courseID)) else {
print("Failed to fetch assignments.")
func fetchAssignmentGroups() async {
guard let courseID = courseID, let (data, _) = try? await CanvasService.shared.fetchResponse(
CanvasRequest.getAssignmentGroups(courseId: courseID)
) else {
print("Failed to fetch assignment groups.")
return
}

self.assignmentGroups = (try? JSONDecoder().decode([AssignmentGroup].self, from: data)) ?? []
}

static func getAssignmentsForCourse(courseID: String) async -> [Assignment] {
await CourseAssignmentManager.fetchAssignments(courseID: courseID)
}

private static func fetchAssignments(courseID: String) async -> [Assignment] {
guard let (data, _) = try? await CanvasService.shared.fetchResponse(CanvasRequest.getAssignments(courseId: courseID)) else {
print("Failed to fetch assignments.")
return []
}

do {
self.assignments = try JSONDecoder().decode([Assignment].self, from: data)
return try JSONDecoder().decode([Assignment].self, from: data)
} catch {
print(error)
}
}

static func getAssignmentsForCourse(courseID: String) async -> [Assignment] {
let manager = CourseAssignmentManager(courseID: courseID)
await manager.fetchAssignments()
return manager.assignments

return []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,20 @@ struct CourseAssignmentsView: View {
}

var body: some View {
List(assignmentManager.assignments, id: \.id) { assignment in
HStack {
VStack(alignment: .leading) {
Text(assignment.name)
.font(.headline)
Group {
if let submission = assignment.submission {
Text(
submission.workflowState?.rawValue.capitalized ?? "Unknown Status"
)
}
}
.font(.subheadline)
List(assignmentManager.assignmentGroups) { assignmentGroup in
Section {
ForEach(assignmentGroup.assignments ?? []) { assignment in
AssignmentRow(assignment: assignment, showGrades: showGrades)
}
} header: {
HStack {
Text(assignmentGroup.name ?? "")

if showGrades, let submission = assignment.submission {
Spacer()

Text(submission.score?.truncatingTrailingZeros ?? "--") +
Text("/") +
Text(assignment.pointsPossible?.truncatingTrailingZeros ?? "--")
if let groupWeight = assignmentGroup.groupWeight {
Text("\(groupWeight)%")
}
}
}
}
Expand All @@ -54,7 +47,37 @@ struct CourseAssignmentsView: View {

private func loadAssignments() async {
isLoadingAssignments = true
await assignmentManager.fetchAssignments()
await assignmentManager.fetchAssignmentGroups()
isLoadingAssignments = false
}
}

private struct AssignmentRow: View {
let assignment: Assignment
let showGrades: Bool

var body: some View {
HStack {
VStack(alignment: .leading) {
Text(assignment.name ?? "")
.font(.headline)
Group {
if let submission = assignment.submission {
Text(
submission.workflowState?.rawValue.capitalized ?? "Unknown Status"
)
}
}
.font(.subheadline)
}

if showGrades, let submission = assignment.submission {
Spacer()

Text(submission.score?.truncatingTrailingZeros ?? "--") +
Text("/") +
Text(assignment.pointsPossible?.truncatingTrailingZeros ?? "--")
}
}
}
}
2 changes: 1 addition & 1 deletion CanvasPlusPlayground/Features/Courses/CourseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class CourseManager {
func getCourses() async {
do {
let courses: [Course] = try await CanvasService.shared.loadAndSync(
CanvasRequest.getCourses(enrollmentState: "active"),
CanvasRequest.getCourses(enrollmentState: "completed"),
onCacheReceive: { cachedCourses in
guard let cachedCourses else { return }
setCourses(cachedCourses)
Expand Down
2 changes: 1 addition & 1 deletion CanvasPlusPlayground/Schema/Plain/Assignment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ import Foundation
"restrict_quantitative_data":false}
*/

struct Assignment: Codable {
struct Assignment: Codable, Identifiable {
let id: Int
let description: String?
let dueAt: String?
Expand Down
42 changes: 42 additions & 0 deletions CanvasPlusPlayground/Schema/Plain/AssignmentGroup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// AssignmentGroup.swift
// CanvasPlusPlayground
//
// Created by Rahul on 12/18/24.
//

import Foundation

struct AssignmentGroup: Codable, Identifiable {
let id: Int?
let name: String?
let position: Int?
let groupWeight: Int?
let sisSourceID: String?
let integrationData: [String: String]?
let assignments: [Assignment]?
let rules: GradingRules?

enum CodingKeys: String, CodingKey {
case id
case name
case position
case groupWeight = "group_weight"
case sisSourceID = "sis_source_id"
case integrationData = "integration_data"
case assignments
case rules
}
}

struct GradingRules: Codable {
let dropLowest: Int?
let dropHighest: Int?
let neverDrop: [Int]?

enum CodingKeys: String, CodingKey {
case dropLowest = "drop_lowest"
case dropHighest = "drop_highest"
case neverDrop = "never_drop"
}
}

0 comments on commit 1d43a3d

Please sign in to comment.