Skip to content

Commit bfe25a4

Browse files
Merge branch 'master' into bugfix/MBL-19564-Domain-Lookup-Search-Enhanced
2 parents ca82015 + cc20553 commit bfe25a4

File tree

53 files changed

+822
-715
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+822
-715
lines changed

Core/Core/Common/CommonModels/AppEnvironment/ExperimentalFeature.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public enum ExperimentalFeature: String, CaseIterable, Codable {
3030
case whatIfScore = "what_if_score"
3131
case rebuiltCalendar = "rebuilt_calendar"
3232
case revertToOldStudentToDo = "revert_to_old_student_todo"
33+
case studentLearnerDashboard = "student_learner_dashboard"
3334

3435
private static var sharedUserDefaults: UserDefaults {
3536
UserDefaults(suiteName: Bundle.main.appGroupID()) ?? .standard

Core/Core/Common/CommonUI/CoreWebView/Model/CoreWebViewStudioFeaturesInteractor.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public class CoreWebViewStudioFeaturesInteractor {
7878
resetStoreSubscription()
7979
}
8080

81-
func urlForStudioImmersiveView(of action: WKNavigationAction) -> URL? {
81+
func urlForStudioImmersiveView(of action: NavigationActionRepresentable) -> URL? {
8282
guard action.isStudioImmersiveViewLinkTap, var url = action.request.url else {
8383
return nil
8484
}
@@ -173,22 +173,22 @@ public class CoreWebViewStudioFeaturesInteractor {
173173

174174
// MARK: - WKNavigationAction Extensions
175175

176-
extension WKNavigationAction {
176+
extension NavigationActionRepresentable {
177177

178178
fileprivate var isStudioImmersiveViewLinkTap: Bool {
179179
guard let path = request.url?.path else { return false }
180180

181181
let isExpandLink =
182-
navigationType == .other
183-
&& path.contains("/media_attachments/") == true
184-
&& path.hasSuffix("/immersive_view") == true
185-
&& sourceFrame.isMainFrame == false
182+
navigationType == .other
183+
&& path.contains("/media_attachments/")
184+
&& path.hasSuffix("/immersive_view")
185+
&& sourceInfoFrame.isMainFrame == false
186186

187187
let isDetailsLink =
188-
navigationType == .linkActivated
189-
&& path.contains("/media_attachments/") == true
190-
&& path.hasSuffix("/immersive_view") == true
191-
&& (targetFrame?.isMainFrame ?? false) == false
188+
navigationType == .linkActivated
189+
&& path.contains("/media_attachments/")
190+
&& path.hasSuffix("/immersive_view")
191+
&& (targetInfoFrame?.isMainFrame ?? false) == false
192192

193193
return isExpandLink || isDetailsLink
194194
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// This file is part of Canvas.
3+
// Copyright (C) 2025-present Instructure, Inc.
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as
7+
// published by the Free Software Foundation, either version 3 of the
8+
// License, or (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
//
18+
19+
import Foundation
20+
import WebKit
21+
22+
protocol FrameInfoRepresentable {
23+
var isMainFrame: Bool { get }
24+
}
25+
26+
protocol NavigationActionRepresentable {
27+
var request: URLRequest { get }
28+
var navigationType: WKNavigationType { get }
29+
var sourceInfoFrame: FrameInfoRepresentable { get }
30+
var targetInfoFrame: FrameInfoRepresentable? { get }
31+
}
32+
33+
extension WKFrameInfo: FrameInfoRepresentable {}
34+
35+
extension WKNavigationAction: NavigationActionRepresentable {
36+
var sourceInfoFrame: FrameInfoRepresentable {
37+
self.sourceFrame
38+
}
39+
40+
var targetInfoFrame: FrameInfoRepresentable? {
41+
self.targetFrame
42+
}
43+
}

Core/Core/Common/CommonUI/CoreWebView/View/CoreWebView.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,10 @@ open class CoreWebView: WKWebView {
495495
)
496496
.store(in: &subscriptions)
497497
}
498+
499+
func isSourceFrameSameAsTarget(_ action: WKNavigationAction) -> Bool {
500+
return action.sourceFrame == action.targetFrame
501+
}
498502
}
499503

500504
// MARK: - WKNavigationDelegate
@@ -526,7 +530,7 @@ extension CoreWebView: WKNavigationDelegate {
526530

527531
// Scroll to fragment if this is a #fragment link click on the same site
528532
if action.navigationType == .linkActivated,
529-
action.sourceFrame == action.targetFrame,
533+
isSourceFrameSameAsTarget(action),
530534
let url = action.request.url, let fragment = url.fragment,
531535
// self.url isn't set when using loadHTMLString, so we check the baseURL that is set when calling that method
532536
let lhsString: String.SubSequence = (self.url ?? baseURL)?.absoluteString.split(separator: "#").first,

Core/Core/Common/Extensions/Foundation/StringExtensions.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ extension String {
6868
}
6969
}
7070

71+
public var containsLetter: Bool {
72+
return unicodeScalars.contains { char in
73+
CharacterSet.letters.contains(char)
74+
}
75+
}
76+
7177
/// - returns: True if the receiver string only contains decimal digits or if the string is empty.
7278
public var containsOnlyNumbers: Bool {
7379
unicodeScalars.allSatisfy { CharacterSet.decimalDigits.contains($0) }

Core/Core/Features/Dashboard/Container/View/DashboardContainerView.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public struct DashboardContainerView: View, ScreenViewTrackable {
158158
}
159159
.frame(width: 44, height: 44).padding(.trailing, -6)
160160
.accessibilityLabel(Text("Dashboard Options", bundle: .core))
161+
.identifier("Dashboard.optionsButton")
161162
.confirmationDialog("", isPresented: $isShowingKebabDialog) {
162163
Button {
163164
if offlineModeViewModel.isOffline {
@@ -168,6 +169,8 @@ public struct DashboardContainerView: View, ScreenViewTrackable {
168169
} label: {
169170
Text("Manage Offline Content", bundle: .core)
170171
}
172+
.identifier("Dashboard.manageOfflineButton")
173+
171174
Button {
172175
guard controller.value.presentedViewController == nil else {
173176
controller.value.presentedViewController?.dismiss(animated: true)
@@ -177,6 +180,7 @@ public struct DashboardContainerView: View, ScreenViewTrackable {
177180
} label: {
178181
Text("Dashboard Settings", bundle: .core)
179182
}
183+
.identifier("Dashboard.settingsButton")
180184
}
181185
}
182186

@@ -195,7 +199,7 @@ public struct DashboardContainerView: View, ScreenViewTrackable {
195199
}
196200
.frame(width: 44, height: 44).padding(.trailing, -6)
197201
.accessibilityLabel(Text("Dashboard settings", bundle: .core))
198-
.accessibilityIdentifier("Dashboard.settingsButton")
202+
.identifier("Dashboard.settingsButton")
199203
}
200204

201205
// MARK: Nav Bar Buttons -

Core/Core/Features/Dashboard/Container/View/DashboardNavigationBar.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ struct DashboardNavigationBar: ViewModifier {
6767

6868
extension View {
6969

70-
func navigationBarDashboard() -> some View {
70+
public func navigationBarDashboard() -> some View {
7171
modifier(DashboardNavigationBar())
7272
}
7373
}

Core/Core/Features/Grades/Model/GradeFormatter.swift

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,23 @@ public class GradeFormatter {
209209
return medium(score: score, grade: grade)
210210
}
211211
case .letter_grade:
212+
213+
/// This is to protect against the case where grade is retrieved from
214+
/// backend as numeric value for an assignment with `letter_grade`
215+
/// grading style.
216+
let isLetterGradeValue = grade?.containsLetter == true
217+
let letterGrade = isLetterGradeValue ? grade : {
218+
if let normalizedScore {
219+
return gradingScheme?.convertNormalizedScoreToLetterGrade(normalizedScore)
220+
}
221+
return nil
222+
}()
223+
212224
switch gradeStyle {
213225
case .short:
214-
return grade
226+
return letterGrade
215227
case .medium:
216-
return medium(score: score, grade: grade)
228+
return medium(score: score, grade: letterGrade)
217229
}
218230
case .not_graded:
219231
return nil

Core/Core/Features/GradingSchemes/Model/Entities/GradingScheme.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ extension GradingScheme {
3838
}
3939

4040
public func convertNormalizedScoreToLetterGrade(_ normalizedScore: Double) -> String? {
41+
// Those meant for 0-points assignments
42+
if normalizedScore == .infinity { return entries.first?.name }
43+
if normalizedScore == (-1 * .infinity) { return entries.last?.name }
44+
if normalizedScore.isNaN { return nil }
45+
4146
// Teachers can add extra points so the "normalized" score can be higher than 1.0. But 10 would be very suspicious.
4247
assert(abs(normalizedScore) < 10)
4348

Core/Core/Features/Inbox/ComposeMessage/View/ComposeMessageView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ public struct ComposeMessageView: View, ScreenViewTrackable {
130130
.sheet(isPresented: $model.isImagePickerVisible) {
131131
AttachmentPickerAssembly.makeImagePicker(onSelect: model.addFile)
132132
}
133-
.sheet(isPresented: $model.isTakePhotoVisible) {
133+
.fullScreenCover(isPresented: $model.isTakePhotoVisible) {
134134
AttachmentPickerAssembly.makeImageRecorder(onSelect: model.addFile)
135-
.interactiveDismissDisabled()
135+
.ignoresSafeArea()
136136
}
137137
.sheet(isPresented: $model.isAudioRecordVisible) {
138138
AttachmentPickerAssembly.makeAudioRecorder(env: model.env, onSelect: model.addFile)

0 commit comments

Comments
 (0)