Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Horizon] Module Sequence View #3076

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Core/Core/LTI/LTITools.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class LTITools: NSObject {
let env: AppEnvironment
let context: Context
let id: String?
let url: URL?
public let url: URL?
let launchType: GetSessionlessLaunchURLRequest.LaunchType?
let isQuizLTI: Bool? // This is optional because not all entry points provide this info
let assignmentID: String?
Expand Down
69 changes: 69 additions & 0 deletions Core/Core/Modules/MarkModuleItemDone.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// This file is part of Canvas.
// Copyright (C) 2025-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/>.
//

import Foundation
import CoreData

public struct MarkModuleItemDone: APIUseCase {
public typealias Model = ModuleItem

// MARK: - Dependencies

public let courseID: String
public let moduleID: String
public let moduleItemID: String
public let done: Bool

// MARK: - Init

public init(
courseID: String,
moduleID: String,
moduleItemID: String,
done: Bool
) {
self.courseID = courseID
self.moduleID = moduleID
self.moduleItemID = moduleItemID
self.done = done
}
public var cacheKey: String?

public var request: PutMarkModuleItemDone {
PutMarkModuleItemDone(
courseID: courseID,
moduleID: moduleID,
moduleItemID: moduleItemID,
done: done
)
}

public func makeRequest(environment: AppEnvironment, completionHandler: @escaping RequestCallback) {
environment.api.makeRequest(request) { response, urlResponse, error in
if error == nil {
NotificationCenter.default.post(name: .moduleItemRequirementCompleted, object: nil)
}
completionHandler(response, urlResponse, error)
}
}
public func write(
response: APINoContent?,
urlResponse: URLResponse?,
to client: NSManagedObjectContext
) { }
}
66 changes: 66 additions & 0 deletions Core/Core/Modules/MarkModuleItemRead.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// This file is part of Canvas.
// Copyright (C) 2025-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/>.
//

import Foundation
import CoreData

public struct MarkModuleItemRead: APIUseCase {
public typealias Model = ModuleItem

// MARK: - Dependencies

public let courseID: String
public let moduleID: String
public let moduleItemID: String

// MARK: - Init

public init(
courseID: String,
moduleID: String,
moduleItemID: String
) {
self.courseID = courseID
self.moduleID = moduleID
self.moduleItemID = moduleItemID
}

public var cacheKey: String?
public var request: PostMarkModuleItemRead {
PostMarkModuleItemRead(
courseID: courseID,
moduleID: moduleID,
moduleItemID: moduleItemID
)
}

public func makeRequest(environment: AppEnvironment, completionHandler: @escaping RequestCallback) {
environment.api.makeRequest(request) { response, urlResponse, error in
if error == nil {
NotificationCenter.default.post(name: .moduleItemRequirementCompleted, object: nil)
}
completionHandler(response, urlResponse, error)
}
}

public func write(
response: APINoContent?,
urlResponse: URLResponse?,
to client: NSManagedObjectContext
) { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,6 @@ public final class ModuleItemDetailsViewController: UIViewController, ColoredNav
}

extension Notification.Name {
static let moduleItemViewDidLoad = Notification.Name(rawValue: "com.instructure.core.notification.ModuleItemViewDidLoad")
public static let moduleItemViewDidLoad = Notification.Name(rawValue: "com.instructure.core.notification.ModuleItemViewDidLoad")
public static let moduleItemRequirementCompleted = Notification.Name(rawValue: "com.instructure.core.notification.ModuleItemRequirementCompleted")
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22155" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
Expand Down Expand Up @@ -79,7 +79,6 @@
<outlet property="buttonsHeightConstraint" destination="ZAv-eD-KKl" id="MIx-pI-9BZ"/>
<outlet property="nextButton" destination="ywu-lr-3Bv" id="uNj-xQ-3iH"/>
<outlet property="pagesContainer" destination="Xc1-o8-iDS" id="pkP-cz-if5"/>
<outlet property="pagesContainerBottomConstraint" destination="VRJ-xc-0QM" id="8s3-RF-X2u"/>
<outlet property="previousButton" destination="lE8-Sf-NiN" id="2lH-2E-P0x"/>
</connections>
</viewController>
Expand All @@ -88,9 +87,17 @@
<point key="canvasLocation" x="-275.36231884057975" y="-218.30357142857142"/>
</scene>
</scenes>
<designables>
<designable name="lE8-Sf-NiN">
<size key="intrinsicContentSize" width="59" height="30"/>
</designable>
<designable name="ywu-lr-3Bv">
<size key="intrinsicContentSize" width="32" height="30"/>
</designable>
</designables>
<resources>
<namedColor name="backgroundLight">
<color red="0.94901960784313721" green="0.95686274509803926" blue="0.95686274509803926" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.96078431372549022" green="0.96078431372549022" blue="0.96078431372549022" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
//

import Foundation
import SwiftUI
import UIKit

public final class ModuleItemSequenceViewController: UIViewController {
Expand All @@ -28,14 +27,12 @@ public final class ModuleItemSequenceViewController: UIViewController {
@IBOutlet weak var buttonsHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var previousButton: UIButton!
@IBOutlet weak var nextButton: UIButton!
@IBOutlet weak var pagesContainerBottomConstraint: NSLayoutConstraint!

/// These should get set only once in viewDidLoad
private var leftBarButtonItems: [UIBarButtonItem]?
private var rightBarButtonItems: [UIBarButtonItem]?

private var env: AppEnvironment = .defaultValue
private lazy var isHorizon = env.app == .horizon
private var courseID: String!
private var assetType: AssetType!
private var assetID: String!
Expand All @@ -48,20 +45,8 @@ public final class ModuleItemSequenceViewController: UIViewController {
private lazy var store = env.subscribe(GetModuleItemSequence(courseID: courseID, assetType: assetType, assetID: assetID)) { [weak self] in
self?.update(embed: true)
}

private var sequence: ModuleItemSequence? { store.first }

private lazy var moduleNavigationViewModel = ModuleBottomNavBarViewModel(
didTapPreviousButton: { [weak self] in
self?.goPrevious()
},
didTapNextButton: { [weak self] in
self?.goNext()
},
router: AppEnvironment.shared.router,
hostingViewController: self
)

public static func create(
env: AppEnvironment,
courseID: String,
Expand All @@ -80,7 +65,7 @@ public final class ModuleItemSequenceViewController: UIViewController {
return controller
}

override public func viewDidLoad() {
public override func viewDidLoad() {
super.viewDidLoad()
leftBarButtonItems = navigationItem.leftBarButtonItems
rightBarButtonItems = navigationItem.rightBarButtonItems
Expand All @@ -89,11 +74,11 @@ public final class ModuleItemSequenceViewController: UIViewController {
pages.scrollView.isScrollEnabled = false
embed(pages, in: pagesContainer)

if isHorizon {
setupModuleNavigationBarForHorizon()
} else {
setupModuleNavigationBarForCanvas()
}
// places the next arrow on the opposite side
let transform = CGAffineTransform(scaleX: -1, y: 1)
nextButton.transform = transform
nextButton.titleLabel?.transform = transform
nextButton.imageView?.transform = transform

// Sometimes module links within Pages are referenced by their pageId ("/pages/my-module") instead of their id.
// When downloading module item sequences for offline usage, we always download with the id field so we need to
Expand All @@ -110,42 +95,6 @@ public final class ModuleItemSequenceViewController: UIViewController {
store.refresh(force: true)
}

override public func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
guard isHorizon else { return }
env.tabBar(isVisible: true)
}

private func setupModuleNavigationBarForHorizon() {
AppEnvironment.shared.tabBar(isVisible: false)
pagesContainerBottomConstraint.isActive = false
buttonsContainer.removeFromSuperview()

let hostingVC = UIHostingController(rootView: ModuleNavBarView(viewModel: moduleNavigationViewModel))

hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
hostingVC.view.backgroundColor = UIColor(hexString: "#FBF5ED")
addChild(hostingVC)
view.addSubview(hostingVC.view)
view.backgroundColor = UIColor(hexString: "#FBF5ED")
NSLayoutConstraint.activate([
hostingVC.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
hostingVC.view.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
hostingVC.view.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
pagesContainer.bottomAnchor.constraint(equalTo: hostingVC.view.topAnchor)

])
hostingVC.didMove(toParent: self)
}

private func setupModuleNavigationBarForCanvas() {
// places the next arrow on the opposite side
let transform = CGAffineTransform(scaleX: -1, y: 1)
nextButton.transform = transform
nextButton.titleLabel?.transform = transform
nextButton.imageView?.transform = transform
}

private func update(embed: Bool) {
if store.requested, store.pending {
return
Expand Down Expand Up @@ -175,18 +124,12 @@ public final class ModuleItemSequenceViewController: UIViewController {
}

private func showSequenceButtons(prev: Bool, next: Bool) {
if isHorizon {
moduleNavigationViewModel.isPreviousButtonEnabled = prev
moduleNavigationViewModel.isNextButtonEnabled = next
} else {
let show = prev || next
buttonsContainer.isHidden = show == false
buttonsHeightConstraint.constant = show ? 56 : 0
previousButton.isHidden = prev == false
nextButton.isHidden = next == false
}

view.layoutIfNeeded()
let show = prev || next
self.buttonsContainer.isHidden = show == false
self.buttonsHeightConstraint.constant = show ? 56 : 0
previousButton.isHidden = prev == false
nextButton.isHidden = next == false
self.view.layoutIfNeeded()
}

private func show(item: ModuleItemSequenceNode, direction: PagesViewController.Direction? = nil) {
Expand Down
2 changes: 1 addition & 1 deletion Core/Core/Pages/GetPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public struct GetPage: UseCase {

var isFrontPage: Bool { url == "front_page" }

init(context: Context, url: String) {
public init(context: Context, url: String) {
self.context = context
self.url = url.removingPercentEncoding ?? ""
}
Expand Down

This file was deleted.

Loading