Skip to content

Commit 1ade88b

Browse files
authored
Horizon Core Changes #5 (#3517)
[ignore-commit-lint] affects: Student, Teacher, Parent
1 parent 4e0955b commit 1ade88b

File tree

70 files changed

+1425
-491
lines changed

Some content is hidden

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

70 files changed

+1425
-491
lines changed

Core/Core/Common/CommonModels/API/API.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ public class API {
119119
-> APITask? {
120120
var request = URLRequest(url: url)
121121

122+
if AppEnvironment.shared.app == .horizon {
123+
let token = AppEnvironment.shared.currentSession?.accessToken ?? ""
124+
request.setValue("Bearer \(token)", forHTTPHeaderField: HttpHeader.authorization)
125+
}
126+
122127
if let method {
123128
request.httpMethod = method.rawValue.uppercased()
124129
}

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

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Combine
2121
import CoreData
2222
import SwiftUI
2323
import WidgetKit
24+
import WebKit
2425

2526
public protocol AppEnvironmentDelegate {
2627
var environment: AppEnvironment { get }
@@ -52,6 +53,8 @@ open class AppEnvironment {
5253
public var lastLoginAccount: APIAccountResult?
5354
public let k5 = K5State()
5455
public weak var loginDelegate: LoginDelegate?
56+
public var userDidLogin: (() -> Void)?
57+
5558
public weak var window: UIWindow?
5659
open var isTest: Bool { false }
5760
private var subscriptions = Set<AnyCancellable>()
@@ -89,13 +92,15 @@ open class AppEnvironment {
8992
refreshWidgets()
9093
saveAccount(for: session)
9194

92-
Just(())
93-
.receive(on: RunLoop.main)
94-
.flatMap { CoreWebView.deleteAllCookies() }
95-
.sink {
96-
CoreWebView.refreshKeepAliveCookies()
97-
}
98-
.store(in: &subscriptions)
95+
if AppEnvironment.shared.app != .horizon {
96+
Just(())
97+
.receive(on: RunLoop.main)
98+
.flatMap { CoreWebView.deleteAllCookies() }
99+
.sink {
100+
CoreWebView.refreshKeepAliveCookies()
101+
}
102+
.store(in: &subscriptions)
103+
}
99104
}
100105

101106
public func userDidLogout(session: LoginSession) {
@@ -109,6 +114,12 @@ open class AppEnvironment {
109114
router.courseTabUrlInteractor?.cancelTabSubscription()
110115
refreshWidgets()
111116
deleteUserData(session: session)
117+
if AppEnvironment.shared.app == .horizon {
118+
// doing cleanup of webviews so that logged in user data is not available
119+
WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
120+
modifiedSince: Date(timeIntervalSince1970: 0),
121+
completionHandler: {})
122+
}
112123
}
113124

114125
public func widgetUserDidLogout() {
@@ -186,6 +197,22 @@ open class AppEnvironment {
186197
UserDefaults.standard.set(data, forKey: "lastLoginAccount")
187198
}
188199

200+
public func tabBar(isVisible: Bool) {
201+
let currentTabBar = (window?.rootViewController as? UITabBarController)
202+
if #available(iOS 18, *) {
203+
currentTabBar?.setTabBarHidden(!isVisible, animated: false)
204+
} else {
205+
currentTabBar?.tabBar.isHidden = !isVisible
206+
}
207+
}
208+
209+
public func navigationBar(isVisible: Bool) {
210+
let currentTabBar = (window?.rootViewController as? UITabBarController)
211+
if let currentNavigation = currentTabBar?.selectedViewController as? UINavigationController {
212+
currentNavigation.setNavigationBarHidden(!isVisible, animated: false)
213+
}
214+
}
215+
189216
public var apiHost: String? {
190217
currentSession?.baseURL.host()
191218
}

Core/Core/Common/CommonModels/AppEnvironment/LocalizationManager.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
//
1818

1919
import UIKit
20+
import Combine
2021

21-
public class LocalizationManager {
22+
public enum LocalizationManager {
2223
private static let instUserLocale = "InstUserLocale"
2324
static var suspend = #selector(NSXPCConnection.suspend)
2425

@@ -77,4 +78,26 @@ public class LocalizationManager {
7778
env.router.show(alert, from: root, options: .modal())
7879
}
7980
}
81+
82+
public static func localizeForApp(
83+
_ app: UIApplication,
84+
locale: String?
85+
) -> AnyPublisher<UIAlertController?, Never> {
86+
setCurrentLocale(locale)
87+
let env = AppEnvironment.shared
88+
guard
89+
needsRestart,
90+
env.window?.rootViewController != nil else {
91+
return Just(nil).eraseToAnyPublisher()
92+
}
93+
let alert = UIAlertController(
94+
title: String(localized: "Updated Language Settings", bundle: .core),
95+
message: String(localized: "The app needs to restart to use the new language settings. Please relaunch the app.", bundle: .core),
96+
preferredStyle: .alert
97+
)
98+
alert.addAction(AlertAction(String(localized: "Close App", bundle: .core), style: .default) { _ in
99+
UIControl().sendAction(suspend, to: app, for: nil)
100+
})
101+
return Just(alert).eraseToAnyPublisher()
102+
}
80103
}

Core/Core/Common/CommonModels/AppEnvironment/Secret.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ public enum Secret {
4242
/// The value used for testing that Secret is working properly
4343
case testSecret
4444

45-
/// The clientID required for OAuth. Currently only Horizon uses it with PKCE login, regular Canvas apps use the Mobile Verify endpoint to obtain clientID (and other required properties).
46-
case appClientID
47-
4845
public var string: String? {
4946
guard let data = NSDataAsset(name: String(describing: self), bundle: .core)?.data else { return nil }
5047
let mixer = [UInt8]("\(String(describing: self))+\(Bundle.core.bundleIdentifier ?? "")".utf8)

Core/Core/Common/CommonModels/AppEnvironment/SessionDefaults.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,13 @@ public struct SessionDefaults: Equatable {
256256
set { self["assignmentListTeacherStatusFilterSettingByCourseId"] = newValue }
257257
}
258258

259+
// MARK: - Horizon
260+
261+
public var assignmentSubmissionTextEntry: [String: String]? {
262+
get { self["assignmentSubmissionTextEntry"] as? [String: String] }
263+
set { self["assignmentSubmissionTextEntry"] = newValue }
264+
}
265+
259266
// MARK: - SpeedGrader
260267

261268
public var isSpeedGraderAnnotationToolbarVisible: Bool? {

Core/Core/Common/CommonModels/Router/Router.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@ open class Router {
220220

221221
// MARK: - View Controller Presentation
222222

223+
open func setRootViewController(isLoginTransition: Bool, viewController: UIViewController) {
224+
guard let window = AppEnvironment.shared.window else { return }
225+
UIView.transition(with: window, duration: 0.5, options: isLoginTransition ? .transitionFlipFromRight : .transitionFlipFromLeft) {
226+
window.rootViewController = viewController
227+
}
228+
}
229+
223230
/**
224231
- parameters:
225232
- analyticsRoute: The route to be reported as screen\_view analytics event. If nil, no route is reported but this is only for internal usage to avoid both the `route` and `show` functions reporting the same event.

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import UIKit
2222

2323
@IBDesignable
2424
open class CoreWebView: WKWebView {
25+
2526
private static var BalsamiqRegularCSSFontFace: String = {
2627
let url = Bundle.core.url(forResource: "font_balsamiq_regular", withExtension: "css")!
2728
// swiftlint:disable:next force_try
@@ -33,6 +34,13 @@ open class CoreWebView: WKWebView {
3334
// swiftlint:disable:next force_try
3435
return try! String(contentsOf: url)
3536
}()
37+
38+
private static var FigtreeRegularCSSFontFace: String = {
39+
let url = Bundle.core.url(forResource: "font_figtree_regular", withExtension: "css")!
40+
// swiftlint:disable:next force_try
41+
return try! String(contentsOf: url)
42+
}()
43+
3644
public static let processPool = WKProcessPool()
3745

3846
@IBInspectable public var autoresizesHeight: Bool = false
@@ -195,7 +203,7 @@ open class CoreWebView: WKWebView {
195203
} }
196204
}
197205

198-
func html(for content: String) -> String {
206+
open func html(for content: String) -> String {
199207
// If it looks like jQuery is used, include the same version of jQuery as web.
200208
let jquery = content.contains("$(") || content.contains("$.")
201209
? "<script defer src=\"https://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js\"></script>"
@@ -279,12 +287,17 @@ open class CoreWebView: WKWebView {
279287
: style.uiFont
280288
let marginsDisabled = features.contains { $0 is DisableDefaultBodyMargin }
281289

282-
if AppEnvironment.shared.k5.isK5Enabled {
283-
font = "BalsamiqSans-Regular"
284-
fontCSS = Self.BalsamiqRegularCSSFontFace
290+
if AppEnvironment.shared.app == .horizon {
291+
font = "Figtree-Regular"
292+
fontCSS = Self.FigtreeRegularCSSFontFace
285293
} else {
286-
font = "Lato-Regular"
287-
fontCSS = Self.LatoRegularCSSFontFace
294+
if AppEnvironment.shared.k5.isK5Enabled {
295+
font = "BalsamiqSans-Regular"
296+
fontCSS = Self.BalsamiqRegularCSSFontFace
297+
} else {
298+
font = "Lato-Regular"
299+
fontCSS = Self.LatoRegularCSSFontFace
300+
}
288301
}
289302

290303
return """
@@ -460,7 +473,7 @@ extension CoreWebView: WKNavigationDelegate {
460473
decisionHandler(.allow)
461474
}
462475

463-
public func webView(
476+
open func webView(
464477
_ webView: WKWebView,
465478
didFinish navigation: WKNavigation!
466479
) {

Core/Core/Common/CommonUI/InstUI/BaseScreen.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public extension InstUI {
4040
public let scrollBounce: ScrollBounceBehavior
4141
public let errorPandaConfig: InteractivePanda.Config
4242
public let emptyPandaConfig: InteractivePanda.Config
43+
public let loaderBackgroundColor: Color
4344

4445
/**
4546
- parameters:
@@ -52,15 +53,18 @@ public extension InstUI {
5253
showsScrollIndicators: Bool = true,
5354
scrollAxes: Axis.Set = .vertical,
5455
scrollBounce: ScrollBounceBehavior = .automatic,
55-
errorPandaConfig: InteractivePanda.Config = .error(),
56-
emptyPandaConfig: InteractivePanda.Config = .empty()
56+
errorPandaConfig: InteractivePanda.Config = AppEnvironment.shared.app == .horizon ? .horizonError() : .error(),
57+
emptyPandaConfig: InteractivePanda.Config = AppEnvironment.shared.app == .horizon ? .horizonEmpty() : .empty(),
58+
loaderBackgroundColor: Color = .backgroundLightest
59+
5760
) {
5861
self.refreshable = refreshable
5962
self.showsScrollIndicators = showsScrollIndicators
6063
self.scrollAxes = scrollAxes
6164
self.scrollBounce = scrollBounce
6265
self.errorPandaConfig = errorPandaConfig
6366
self.emptyPandaConfig = emptyPandaConfig
67+
self.loaderBackgroundColor = loaderBackgroundColor
6468
}
6569
}
6670

@@ -120,7 +124,7 @@ public extension InstUI {
120124
.frame(maxWidth: .infinity, maxHeight: .infinity)
121125
.background(showOverlay
122126
? Color.backgroundGrouped.opacity(0.5)
123-
: Color.backgroundLightest
127+
: config.loaderBackgroundColor
124128
)
125129
}
126130

@@ -165,8 +169,8 @@ public extension InstUI {
165169
.frame(width: geometry.size.width, height: geometry.size.height)
166170
}
167171
}
172+
.background(AppEnvironment.shared.app == .horizon ? Color.clear : Color.backgroundLightest)
168173
.scrollBounceBehavior(config.scrollBounce)
169-
.background(Color.backgroundLightest)
170174
}
171175
}
172176
}

Core/Core/Common/CommonUI/NavigationBar/UIKit/UINavigationBarExtensions.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@ extension UINavigationBar {
4646
}
4747

4848
public func useGlobalNavStyle(brand: Brand = Brand.shared) {
49-
let background = brand.navBackground
50-
let foreground = brand.navTextColor
49+
// TODO: Remove the isHorizon condition once horizon-specific logic is no longer needed.
50+
let isHorizon = AppEnvironment.shared.app == .horizon
51+
let background: UIColor = isHorizon ? .backgroundLightest : brand.navBackground
52+
let foreground: UIColor = isHorizon ? .backgroundDarkest : brand.navTextColor
5153
titleTextAttributes = [.foregroundColor: foreground]
5254
tintColor = foreground
5355
barTintColor = background
@@ -99,4 +101,20 @@ extension UINavigationBar {
99101
standardAppearance = appearance
100102
scrollEdgeAppearance = standardAppearance
101103
}
104+
105+
// TODO: Remove the isHorizon condition once horizon-specific logic is no longer needed.
106+
private func clearNavigation() {
107+
let appearance = UINavigationBarAppearance()
108+
appearance.configureWithTransparentBackground()
109+
appearance.backgroundColor = .backgroundLightest
110+
appearance.shadowColor = .backgroundLightest
111+
appearance.titleTextAttributes = [.foregroundColor: UIColor.textDarkest]
112+
113+
tintColor = UIColor.textDarkest
114+
standardAppearance = appearance
115+
scrollEdgeAppearance = appearance
116+
compactAppearance = appearance
117+
isTranslucent = true
118+
self.backgroundColor = .backgroundLightest
119+
}
102120
}

Core/Core/Common/Extensions/Foundation/URLExtensions.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,24 @@ public extension Array where Element == URL {
302302
}
303303
}
304304
}
305+
306+
public extension URL {
307+
func replaceHostWithCanvasForCareer() -> URL? {
308+
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false) else {
309+
return nil
310+
}
311+
var newComponents = components
312+
newComponents.host = newComponents.host?.replaceHostWithCanvasForCareer()
313+
return newComponents.url
314+
}
315+
}
316+
317+
public extension String {
318+
func replaceHostWithCanvasForCareer() -> String {
319+
let newString = replacing("horizon.cd.instructure.com", with: "dev.cd.canvashorizon.com")
320+
if let range = newString.range(of: "instructure.com") {
321+
return newString.replacingCharacters(in: range, with: "canvasforcareer.com")
322+
}
323+
return newString
324+
}
325+
}

0 commit comments

Comments
 (0)