Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
8ff9f40
Update translations (#2929)
inst-danger Oct 15, 2024
5645dab
Change letter grade when toggling based on graded assignments on grad…
Ahmed-Naguib93 Oct 15, 2024
00c1531
Add baseURL to webview. (#2924)
vargaat Oct 15, 2024
f61c4dd
Fix file parsing failing when filename is null. Fix offline content s…
vargaat Oct 15, 2024
4dee1cd
Upload dSYMs (#2922)
vargaat Oct 15, 2024
bbd7048
Enable sending messages to an unlimited number of recipients (#2923)
Ahmed-Naguib93 Oct 16, 2024
8a720cc
Move Student QuizDetails to Core (#2937)
szabinst Oct 18, 2024
d32428b
Fix Observer Alert list (#2925)
rh12 Oct 21, 2024
96f0836
Remove unnecessary color contrast calculation. (#2939)
vargaat Oct 21, 2024
42959c9
Fixed an issue where pasting long text and typing in the middle moved…
Ahmed-Naguib93 Oct 21, 2024
ac89807
Calendar Filter crash (#2938)
rh12 Oct 21, 2024
7f74b69
Update CODEOWNERS
balintbartok Oct 21, 2024
17f75e0
Fix issues of Xcode 16 (#2899)
suhaibabsi-inst Oct 24, 2024
e7814a6
Show media from media comment in the Inbox (#2940)
Ahmed-Naguib93 Oct 24, 2024
c7b074d
Show assignments in grade list depend on hideInGradeBook flag (#2943)
Ahmed-Naguib93 Oct 24, 2024
c47b62e
Automation - E2E test maintenance (#2945)
ndrsszsz Oct 24, 2024
d1ade3d
Enable add recipients when reply from web (#2931)
Ahmed-Naguib93 Oct 25, 2024
415c02d
Calendar event custom frequency prefill (#2946)
rh12 Oct 28, 2024
49d9fff
No file verifiers (#2941)
vargaat Oct 28, 2024
54c0bb9
Update translations (#2953)
inst-danger Oct 28, 2024
66815c5
Fix the issue of missing Indicator of unread messages on Inbox icon (…
suhaibabsi-inst Oct 30, 2024
4548dd4
Fix the issue of annotation bar move button being cut off on landscap…
suhaibabsi-inst Oct 30, 2024
8466bb5
Update GetWebSessionRequest to support masquerading (#2958)
rh12 Nov 4, 2024
04462c3
Swift Style Guide proposal (#2915)
rh12 Nov 5, 2024
8cae588
Implement Assignments Offline E2E test case (#2956)
kdeakinstructure Nov 5, 2024
cb2f937
Assignment List Preferences (Filter, GroupBy, Grading Period) (#2948)
ndrsszsz Nov 6, 2024
48b719f
Fix offline file loading logic (#2960)
vargaat Nov 6, 2024
49f16e3
Startup crash on iPads running iOS 18 (#2966)
rh12 Nov 11, 2024
b69f334
Update translations (#2964)
inst-danger Nov 13, 2024
7016061
Inbox messages truncated (#2968)
rh12 Nov 13, 2024
783299d
Update README.md
balintbartok Nov 18, 2024
b2a366c
Fix E2E tests (#2970)
ndrsszsz Nov 18, 2024
03463c6
Update translations (#2973)
inst-danger Nov 19, 2024
8bd60f2
Fix the issue of shown status for "No Submission" & "On Paper" assig…
suhaibabsi-inst Nov 19, 2024
a42b601
Calendar View SwiftUI Rebuild (#2903)
suhaibabsi-inst Nov 19, 2024
330441e
Fix navigation crash (#2963)
vargaat Nov 19, 2024
d9a5be6
E2E test maintenance
ndrsszsz Nov 22, 2024
bb131bf
Fix issue with assignments without grading period not being shown on …
ndrsszsz Nov 22, 2024
89d55ac
E2E Test Maintenance (#2987)
ndrsszsz Nov 27, 2024
1a84527
Fix crash of audio player showing infinite time (#2984)
suhaibabsi-inst Nov 28, 2024
a7b72ff
Improve developer support (#2978)
vargaat Nov 28, 2024
3af8095
Preserve PDF annotations even when cache is reset (#2965)
suhaibabsi-inst Nov 29, 2024
ae4124e
Escape quotation mark when uploading a file. (#2985)
vargaat Nov 29, 2024
cb82cf2
Fix comment library not showing up when tapping on the comment field …
vargaat Nov 29, 2024
1aa7059
Fix media in offline rich content (#2977)
vargaat Nov 29, 2024
c00243e
Restrict disabled tabs (#2986)
rh12 Dec 2, 2024
5cf8ff4
Courses Smart Search Bar (#2932)
suhaibabsi-inst Dec 2, 2024
a2a89b2
Update environment settings API call according to the new format. (#2…
vargaat Dec 2, 2024
014347f
Fix iOS 18 iPad issues (#2983)
vargaat Dec 3, 2024
763b616
refs: MBL-18146 (#3002)
balintbartok Dec 4, 2024
e22cbc7
Fix flaky tests (#3000)
vargaat Dec 4, 2024
ba53a09
Fix the issue of Tab bar covering out color-palette-chooser while ann…
suhaibabsi-inst Dec 5, 2024
ad2163f
Assignment List Screen Improvements (#2996)
ndrsszsz Dec 5, 2024
0e245f2
Fix issue of user not being able to submit assignment when logged in …
suhaibabsi-inst Dec 5, 2024
4179699
New Quiz LTI labeling (#2999)
rh12 Dec 5, 2024
8f5f733
Fix disabled Home page in SplitView details (#3023)
rh12 Dec 12, 2024
861b4f2
Update translations (#3010)
inst-danger Dec 16, 2024
4133596
Fix false positives for unknown tab format logging (#3007)
rh12 Dec 16, 2024
9d45d1c
Fix issue of Groups of Unpublished Courses showing in All Courses scr…
suhaibabsi-inst Dec 16, 2024
13ce291
Fix player viewcontroller not getting deallocated. (#3008)
vargaat Dec 16, 2024
d407376
Update translations (#3030)
inst-danger Dec 16, 2024
7cd7cb3
New Quiz LTI in dedicated web view (#3027)
rh12 Dec 16, 2024
ff979dd
Fix Appearance (Light/Dark Mode) Issues of Course Smart Search (#3016)
suhaibabsi-inst Dec 16, 2024
a745be9
Fix Course Smart Search UI Issues (Keyboard scrolls header) (#3017)
suhaibabsi-inst Dec 16, 2024
317f92f
Assignment List and List Preferences Improvements (#3021)
ndrsszsz Dec 16, 2024
d889cf0
Merge remote-tracking branch 'origin/master' into feature/horizon-mer…
szabinst Dec 17, 2024
39d067a
fix: resolve conflicts
szabinst Dec 17, 2024
49a3e34
Move SubmissionController to core module (#3033)
Ahmed-Naguib93 Dec 17, 2024
40e23c2
Merge branch 'master' into feature/horizon-merge-master
szabinst Dec 17, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ public struct AssignmentDetailsView: View, ScreenViewTrackable {
id: assignment.first?.externalToolContentID,
url: nil,
launchType: "assessment",
isQuizLTI: assignment.first?.isQuizLTI,
assignmentID: assignmentID,
from: controller.value
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public struct AssignmentListPreferencesScreen: View {
}
}
}
.background(Color.backgroundLightest)
.navigationTitleStyled(navBarTitleView)
.navigationBarItems(leading: cancelButton, trailing: doneButton)
.onDisappear {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,12 @@ public struct AssignmentListScreen: View, ScreenViewTrackable {
}
}

private func setupDefaultSplitDetailView(_ route: String) {
guard let defaultViewProvider = controller.value as? DefaultViewProvider, defaultViewProvider.defaultViewRoute != route else { return }
defaultViewProvider.defaultViewRoute = route
private func setupDefaultSplitDetailView(_ routeUrl: String) {
guard let defaultViewProvider = controller.value as? DefaultViewProvider,
defaultViewProvider.defaultViewRoute?.url != routeUrl
else { return }

defaultViewProvider.defaultViewRoute = .init(url: routeUrl)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public class AssignmentListViewModel: ObservableObject {
}

private var assignmentGroups: Store<GetAssignmentsByGroup>?
private var wasAssignmentGroupsUpdated: Bool = false

/** This is required for the router to help decide if the hybrid discussion details or the native one should be launched. */
private lazy var featureFlags = env.subscribe(GetEnabledFeatureFlags(context: .course(courseID)))
Expand All @@ -133,13 +134,13 @@ public class AssignmentListViewModel: ObservableObject {

loadAssignmentListPreferences()
featureFlags.refresh()
course.refresh()
gradingPeriods.refresh(force: true)
}

// MARK: - Functions

public func viewDidAppear() {
gradingPeriods.refresh()
course.refresh()
func viewDidAppear() {
isFilterIconSolid = isFilteringCustom || selectedGradingPeriodId != defaultGradingPeriodId
}

Expand All @@ -162,7 +163,6 @@ public class AssignmentListViewModel: ObservableObject {
}

filterOptionsDidUpdate(filterOptionsStudent: selectedFilterOptionsStudent, gradingPeriodId: selectedGradingPeriodId)
assignmentGroups?.refresh()
}

func filterOptionsDidUpdate(
Expand Down Expand Up @@ -195,12 +195,20 @@ public class AssignmentListViewModel: ObservableObject {
assignmentGroups = env.subscribe(GetAssignmentsByGroup(courseID: courseID, gradingPeriodID: selectedGradingPeriodId)) { [weak self] in
self?.assignmentGroupsDidUpdate()
}

assignmentGroups?.refresh()
}

private func assignmentGroupsDidUpdate() {
guard let assignmentGroups else { return }
if !assignmentGroups.requested || assignmentGroups.pending || !gradingPeriods.requested || gradingPeriods.pending { return }

if !wasAssignmentGroupsUpdated, assignmentGroups.isEmpty {
wasAssignmentGroupsUpdated = true
assignmentGroups.refresh(force: true)
return
}

isShowingGradingPeriods = gradingPeriods.count > 1
var assignmentGroupViewModels: [AssignmentGroupViewModel] = []
let assignments: [Assignment]
Expand Down Expand Up @@ -332,7 +340,6 @@ public class AssignmentListViewModel: ObservableObject {
sortingOption: assignmentListPreferences.sortingOption,
gradingPeriodId: assignmentListPreferences.gradingPeriodId
)
assignmentGroups?.refresh()
saveAssignmentListPreferences()
})
let controller = CoreHostingController(AssignmentListPreferencesScreen(viewModel: viewModel))
Expand All @@ -350,33 +357,28 @@ public class AssignmentListViewModel: ObservableObject {
}

private func loadAssignmentListPreferences() {
guard let filterSettingsData = userDefaults?.assignmentListStudentFilterSettingsByCourseId?[courseID] else {
return
}
guard let userDefaults else { return }

guard let customFilterSettingData = userDefaults?.assignmentListTeacherFilterSettingByCourseId?[courseID] else {
return
if let savedStudentFilterOptionIds = userDefaults.assignmentListStudentFilterSettingsByCourseId?[courseID] {
selectedFilterOptionsStudent = savedStudentFilterOptionIds.compactMap { id in
AssignmentFilterOptionStudent.allCases.first { $0.id == id }
}
}

guard let statusFilterSettingData = userDefaults?.assignmentListTeacherStatusFilterSettingByCourseId?[courseID] else {
return
if let savedTeacherFilterOptionId = userDefaults.assignmentListTeacherFilterSettingByCourseId?[courseID],
let savedTeacherFilterOption = AssignmentFilterOptionsTeacher(rawValue: savedTeacherFilterOptionId) {
selectedFilterOptionTeacher = savedTeacherFilterOption
}

guard let groupBySettingData = userDefaults?.assignmentListGroupBySettingByCourseId?[courseID] else {
return
if let savedStatusFilterOptionId = userDefaults.assignmentListTeacherStatusFilterSettingByCourseId?[courseID],
let savedStatusFilterOption = AssignmentStatusFilterOptionsTeacher(rawValue: savedStatusFilterOptionId) {
selectedStatusFilterOptionTeacher = savedStatusFilterOption
}

selectedFilterOptionsStudent = AssignmentFilterOptionStudent.allCases.filter { filterSettingsData.contains($0.id) }

selectedFilterOptionTeacher = AssignmentFilterOptionsTeacher.allCases.filter {
customFilterSettingData == $0.rawValue
}.first ?? selectedFilterOptionTeacher

selectedStatusFilterOptionTeacher = AssignmentStatusFilterOptionsTeacher.allCases.filter {
statusFilterSettingData == $0.rawValue
}.first ?? selectedStatusFilterOptionTeacher

selectedSortingOption = sortingOptions.filter { groupBySettingData == $0.rawValue }.first ?? selectedSortingOption
if let savedGroupByOptionId = userDefaults.assignmentListGroupBySettingByCourseId?[courseID],
let savedGroupByOption = AssignmentArrangementOptions(rawValue: savedGroupByOptionId) {
selectedSortingOption = savedGroupByOption
}
}

private func saveAssignmentListPreferences() {
Expand Down
14 changes: 14 additions & 0 deletions Core/Core/Contexts/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//

import Foundation
import CoreData

public enum ContextType: String, Codable {
case account, course, group, user, section, folder
Expand Down Expand Up @@ -95,3 +96,16 @@ public extension Context {
return context.id.localID == id.localID
}
}

public extension Context {

func color(in client: NSManagedObjectContext) -> UIColor? {
contextColor(in: client)?.color
}

func contextColor(in client: NSManagedObjectContext) -> ContextColor? {
client.fetch(
scope: .where(#keyPath(ContextColor.canvasContextID), equals: canvasContextID)
).first
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// This file is part of Canvas.
// Copyright (C) 2024-present Instructure, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

private class HideReturnButtonInQuizLTI: CoreWebViewFeature {
private let script: String = {
let css = """
a[data-automation="sdk-return-button"] {
display: none;
}
"""

let cssString = css.components(separatedBy: .newlines).joined()
return """
var element = document.createElement('style');
element.innerHTML = '\(cssString)';
document.head.appendChild(element);
"""
}()

public override init() {}

override func apply(on webView: CoreWebView) {
webView.addScript(script)
}
}

public extension CoreWebViewFeature {

/// This feature hides the "Return" button in QuizLTI webviews, based on it's `data-automation` id.
/// The button normally leads the user back to the course home,
/// but in the app we can dismiss the screen natively and the "Return" button navigation would cause issues.
static var hideReturnButtonInQuizLTI: CoreWebViewFeature {
HideReturnButtonInQuizLTI()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public final class CDAllCoursesGroupItem: NSManagedObject, WriteableModel {

@NSManaged public var concluded: Bool
@NSManaged public var isFavorite: Bool
@NSManaged public var isAccessible: Bool

public var context: Context? {
get { contextRaw.flatMap { Context(canvasContextID: $0) } }
Expand Down Expand Up @@ -61,6 +62,7 @@ public final class CDAllCoursesGroupItem: NSManagedObject, WriteableModel {

model.concluded = item.concluded
model.isFavorite = item.is_favorite ?? true
model.isAccessible = item.can_access ?? false

return model
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ public class GroupListInteractorLive: GroupListInteractor {
return groupListStore
.getEntities(keepObservingDatabaseChanges: true)
.filter(with: searchQuery)
.map { $0.map { AllCoursesGroupItem(from: $0) }}
.map { groups in
groups
.filter { $0.isAccessible }
.map { AllCoursesGroupItem(from: $0) }
}
.eraseToAnyPublisher()
}

Expand Down
16 changes: 12 additions & 4 deletions Core/Core/Courses/CourseDetails/View/CourseDetailsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ public struct CourseDetailsView: View, ScreenViewTrackable {
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.background(Color.backgroundLightest)
.padding(.top, headerViewModel.shouldShowHeader(for: geometry.size.height) ? headerViewModel.height : 0)
.padding(.top, headerViewModel.visibleHeight)
// Save the frame of the content so we can inspect its y position and move course image based on that
.transformAnchorPreference(key: ViewBoundsKey.self, value: .bounds) { preferences, bounds in
preferences = [.init(viewId: 0, bounds: geometry[bounds])]
Expand All @@ -180,14 +180,22 @@ public struct CourseDetailsView: View, ScreenViewTrackable {

@ViewBuilder
private func imageHeader(geometry: GeometryProxy) -> some View {
if headerViewModel.shouldShowHeader(for: geometry.size.height) {
if headerViewModel.shouldShowHeader(in: geometry.size) {
CourseDetailsHeaderView(viewModel: headerViewModel, width: geometry.size.width)
}
}

private func setupDefaultSplitDetailView(_ url: URL?) {
if let defaultViewProvider = controller.value as? DefaultViewProvider, defaultViewProvider.defaultViewRoute != url?.absoluteString {
defaultViewProvider.defaultViewRoute = url?.absoluteString
let routeUrl = url?.absoluteString
guard let defaultViewProvider = controller.value as? DefaultViewProvider,
defaultViewProvider.defaultViewRoute?.url != routeUrl
else { return }

defaultViewProvider.defaultViewRoute = routeUrl.map {
.init(
url: $0,
userInfo: [CourseTabUrlInteractor.blockDisabledTabUserInfoKey: false]
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class LTICellViewModel: CourseDetailsCellViewModel {
id: nil,
url: url,
launchType: nil,
isQuizLTI: false,
assignmentID: nil,
from: viewController.value
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public class CourseDetailsHeaderViewModel: ObservableObject {
self?.hideColorOverlay = self?.settings.first?.hideDashcardColorOverlays == true
}

private var shouldShow: Bool = false
private var checkedWidth: CGFloat = .nan
private var keyboard = KeyboardObserved()

public func viewDidAppear() {
settings.refresh()
}
Expand All @@ -56,8 +60,17 @@ public class CourseDetailsHeaderViewModel: ObservableObject {
scrollPositionYChanged(to: frame.minY)
}

public func shouldShowHeader(for height: CGFloat) -> Bool {
self.height < height / 2
public func shouldShowHeader(in availableSize: CGSize) -> Bool {
let isRotating = checkedWidth.isFinite && checkedWidth != availableSize.width
guard isRotating || keyboard.isHiding else { return shouldShow }

shouldShow = self.height < availableSize.height / 2
checkedWidth = availableSize.width
return shouldShow
}

public var visibleHeight: CGFloat {
shouldShow ? height : 0
}

private func scrollPositionYChanged(to value: CGFloat) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ struct CourseSearchResultRowView: View {
}

private var accentColor: Color {
Color(uiColor: searchContext.accentColor ?? .textDarkest)
searchContext.accentColor?.asColor ?? .textDarkest
}

private var titleColor: Color {
let visitedColor = searchContext.accentColor ?? .textDarkest
return Color(uiColor: isVisited ? visitedColor : .textDarkest)
let visitedColor: Color = searchContext.accentColor?.asColor ?? .textDarkest
return isVisited ? visitedColor : .textDarkest
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public struct CourseSmartSearchFilterEditorView: View {
}

private var contextColor: Color {
return Color(uiColor: searchContext.accentColor ?? .textDarkest)
return searchContext.accentColor?.asColor ?? .textDarkest
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public struct CourseSmartSearchHelpView: View {
}

private var contextColor: Color {
return Color(uiColor: searchContext.accentColor ?? .textDarkest)
return searchContext.accentColor?.asColor ?? .textDarkest
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="24B91" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="23H222" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AccountNotification" representedClassName="Core.AccountNotification" syncable="YES">
<attribute name="endAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="iconRaw" attributeType="String"/>
Expand Down Expand Up @@ -154,6 +154,7 @@
<attribute name="courseRoles" optional="YES" attributeType="String"/>
<attribute name="courseTermName" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="String"/>
<attribute name="isAccessible" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="isFavorite" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="name" attributeType="String"/>
</entity>
Expand Down
3 changes: 3 additions & 0 deletions Core/Core/Extensions/UIColorExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//

import Foundation
import SwiftUI
import UIKit

extension UIColor {
Expand Down Expand Up @@ -208,4 +209,6 @@ extension UIColor {
}
return color
}

public var asColor: Color { Color(self) }
}
2 changes: 1 addition & 1 deletion Core/Core/Grades/GradeListAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public enum GradListAssembly {
router: env.router
)
let viewController = CoreHostingController(GradeListView(viewModel: viewModel))
viewController.defaultViewRoute = "/empty"
viewController.defaultViewRoute = .init(url: "/empty")
return viewController
}

Expand Down
Loading