From e628e2aabc151935aaefcec8701189e0431db652 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Mon, 10 Jul 2023 11:00:57 +0500 Subject: [PATCH] chore: handle landscape for new dashboard and component navigation (#1769) --- Source/AccessibilityCLButton.swift | 7 +- Source/CourseContentPageViewController.swift | 7 +- Source/CourseDashboardHeaderView.swift | 5 ++ Source/CutomePlayer/VideoPlayer.swift | 10 ++- Source/NewCourseContentController.swift | 66 +++++++++++------- Source/NewCourseDashboardViewController.swift | 68 ++++++++++++++----- Source/OEXRouter+Swift.swift | 2 +- Source/UIViewController+CommonAdditions.swift | 10 +++ Source/VideoBlockViewController.swift | 35 +++++++--- 9 files changed, 145 insertions(+), 65 deletions(-) diff --git a/Source/AccessibilityCLButton.swift b/Source/AccessibilityCLButton.swift index fef2b984b3..37aa97a944 100644 --- a/Source/AccessibilityCLButton.swift +++ b/Source/AccessibilityCLButton.swift @@ -34,9 +34,12 @@ class AccessibilityCLButton: CustomPlayerButton { } public override func draw(_ rect: CGRect) { - let r = UIBezierPath(ovalIn: rect) + let diameter = min(rect.width, rect.height) + let circleRect = CGRect(x: rect.origin.x, y: rect.origin.y, width: diameter, height: diameter) + let path = UIBezierPath(ovalIn: circleRect) UIColor.black.withAlphaComponent(0.65).setFill() - r.fill() + path.fill() + super.draw(rect) } } diff --git a/Source/CourseContentPageViewController.swift b/Source/CourseContentPageViewController.swift index 72fac2107a..1da0c37233 100644 --- a/Source/CourseContentPageViewController.swift +++ b/Source/CourseContentPageViewController.swift @@ -434,18 +434,13 @@ public class CourseContentPageViewController : UIPageViewController, UIPageViewC guard let nextController = controllerForBlock(block: block) else { return } - setPageControllers(with: [nextController], direction: direction, animated: true) { [weak self] finished in + setPageControllers(with: [nextController], direction: direction, animated: false) { [weak self] finished in guard let weakSelf = self else { return } weakSelf.updateTransitionState(is: false) if weakSelf.shouldCelebrationAppear { weakSelf.showCelebratoryModal(direction: direction, overController: nextController) } } - - currentPageItemIndex = contentLoader.value?.currentIndex() ?? 0 - - updateNavigationBars() - navigationDelegate?.courseContentPageViewController(controller: self, enteredBlockWithID: cursor.current.block.blockID, parentID: cursor.current.parent) } func controllerForBlock(block : CourseBlock, shouldCelebrationAppear: Bool = false) -> UIViewController? { diff --git a/Source/CourseDashboardHeaderView.swift b/Source/CourseDashboardHeaderView.swift index 7994d082c0..789b828390 100644 --- a/Source/CourseDashboardHeaderView.swift +++ b/Source/CourseDashboardHeaderView.swift @@ -16,6 +16,7 @@ protocol CourseDashboardHeaderViewDelegate: AnyObject { } enum HeaderViewState { + case initial case animating case expanded case collapsed @@ -37,6 +38,8 @@ class CourseDashboardHeaderView: UIView { private lazy var datesBannerView = NewCourseDateBannerView() private var bannerInfo: DatesBannerInfo? = nil + var state: HeaderViewState = .initial + private lazy var orgLabel: UILabel = { let label = UILabel() label.accessibilityIdentifier = "CourseDashboardHeaderView:org-label" @@ -245,6 +248,8 @@ class CourseDashboardHeaderView: UIView { make.trailing.equalTo(closeButton.snp.leading).offset(-StandardHorizontalMargin) } + if state == .collapsed { return } + courseInfoContainerView.snp.remakeConstraints { make in make.top.equalTo(closeButton.snp.bottom) make.leading.equalTo(containerView).offset(StandardHorizontalMargin) diff --git a/Source/CutomePlayer/VideoPlayer.swift b/Source/CutomePlayer/VideoPlayer.swift index 87a3599ffd..e503032083 100644 --- a/Source/CutomePlayer/VideoPlayer.swift +++ b/Source/CutomePlayer/VideoPlayer.swift @@ -31,7 +31,7 @@ protocol VideoPlayerDelegate: AnyObject { private var playbackLikelyToKeepUpContext = 0 class VideoPlayer: UIViewController,VideoPlayerControlsDelegate,TranscriptManagerDelegate { - typealias Environment = OEXInterfaceProvider & OEXAnalyticsProvider & OEXStylesProvider + typealias Environment = OEXInterfaceProvider & OEXAnalyticsProvider & OEXStylesProvider & OEXConfigProvider public let environment : Environment fileprivate var controls: VideoPlayerControls? @@ -684,9 +684,13 @@ class VideoPlayer: UIViewController,VideoPlayerControlsDelegate,TranscriptManage func setFullscreen(fullscreen: Bool, animated: Bool, with deviceOrientation: UIInterfaceOrientation, forceRotate rotate: Bool) { if !isVisible { return } isFullScreen = fullscreen + if fullscreen { - - fullScreenContainerView = UIApplication.shared.window?.rootViewController?.view ?? UIApplication.shared.windows[0].rootViewController?.view + if environment.config.isNewComponentNavigationEnabled { + fullScreenContainerView = parent?.findParentViewController(type: NewCourseContentController.self)?.view ?? UIApplication.shared.windows.first?.rootViewController?.view + } else { + fullScreenContainerView = UIApplication.shared.window?.rootViewController?.view ?? UIApplication.shared.windows.first?.rootViewController?.view + } if movieBackgroundView.frame == .zero { movieBackgroundView.frame = movieBackgroundFrame diff --git a/Source/NewCourseContentController.swift b/Source/NewCourseContentController.swift index d5a76a19c0..06a30d4e31 100644 --- a/Source/NewCourseContentController.swift +++ b/Source/NewCourseContentController.swift @@ -63,12 +63,14 @@ class NewCourseContentController: UIViewController, InterfaceOrientationOverridi private let parentID: CourseBlockID? private let courseID: CourseBlockID private let courseQuerier: CourseOutlineQuerier + private let courseOutlineMode: CourseOutlineMode - init(environment: Environment, blockID: CourseBlockID?, resumeCourseBlockID: CourseBlockID? = nil, parentID: CourseBlockID? = nil, courseID: CourseBlockID) { + init(environment: Environment, blockID: CourseBlockID?, resumeCourseBlockID: CourseBlockID? = nil, parentID: CourseBlockID? = nil, courseID: CourseBlockID, courseOutlineMode: CourseOutlineMode? = .full) { self.environment = environment self.blockID = blockID self.parentID = parentID self.courseID = courseID + self.courseOutlineMode = courseOutlineMode ?? .full courseQuerier = environment.dataManager.courseDataManager.querierForCourseWithID(courseID: courseID, environment: environment) super.init(nibName: nil, bundle: nil) @@ -77,7 +79,6 @@ class NewCourseContentController: UIViewController, InterfaceOrientationOverridi } else { findCourseBlockToShow() } - setStatusBar(color: environment.styles.primaryLightColor()) } required init?(coder aDecoder: NSCoder) { @@ -99,6 +100,7 @@ class NewCourseContentController: UIViewController, InterfaceOrientationOverridi override func viewDidLoad() { super.viewDidLoad() + setStatusBar(color: environment.styles.primaryLightColor()) addSubViews() setupComponentView() setupCompletedBlocksView() @@ -150,7 +152,7 @@ class NewCourseContentController: UIViewController, InterfaceOrientationOverridi let parent = courseQuerier.parentOfBlockWith(id: currentBlock.blockID).firstSuccess().value else { return } - let courseContentViewController = CourseContentPageViewController(environment: environment, courseID: courseID, rootID: parent.blockID, initialChildID: currentBlock.blockID, forMode: .full) + let courseContentViewController = CourseContentPageViewController(environment: environment, courseID: courseID, rootID: parent.blockID, initialChildID: currentBlock.blockID, forMode: courseOutlineMode) courseContentViewController.navigationDelegate = self let childViewController = ForwardingNavigationController(rootViewController: courseContentViewController) @@ -170,20 +172,15 @@ class NewCourseContentController: UIViewController, InterfaceOrientationOverridi private func setupCompletedBlocksView() { guard let block = currentBlock, - let section = courseQuerier.parentOfBlockWith(id: block.blockID, type: .Section).firstSuccess().value + let section = courseQuerier.parentOfBlockWith(id: block.blockID, type: .Section).firstSuccess().value, + let sectionChildren = courseQuerier.childrenOfBlockWithID(blockID: section.blockID, forMode: courseOutlineMode).value else { return } - let childBlocks: [CourseBlock] = section.children - .compactMap { item -> CourseBlock? in - return courseQuerier.blockWithID(id: item).firstSuccess().value - } - .flatMap { item -> [CourseBlockID] in - return item.children - } - .compactMap { item -> CourseBlock? in - return courseQuerier.blockWithID(id: item).firstSuccess().value - } - + let childBlocks: [CourseBlock] = sectionChildren.children.compactMap { item in + courseQuerier.childrenOfBlockWithID(blockID: item.blockID, forMode: courseOutlineMode) + .firstSuccess().value?.children ?? [] + }.flatMap { $0 } + let childViews: [UIView] = childBlocks.map { block -> UIView in let view = UIView() view.backgroundColor = block.isCompleted ? environment.styles.accentBColor() : environment.styles.neutralDark() @@ -200,15 +197,13 @@ class NewCourseContentController: UIViewController, InterfaceOrientationOverridi .firstSuccess().value?.children.compactMap({ $0 }).filter({ $0.type == .Unit }) else { return } - guard let firstInCompleteBlock = childBlocks.flatMap({ $0.children }) - .compactMap({ courseQuerier.blockWithID(id: $0).value }) - .first(where: { !$0.isCompleted }) - else { - currentBlock = courseQuerier.childrenOfBlockWithID(blockID: childBlocks.last?.blockID, forMode: .full).firstSuccess().value?.children.last - return + let blocks: [CourseBlock] = childBlocks.flatMap { block in + courseQuerier.childrenOfBlockWithID(blockID: block.blockID, forMode: courseOutlineMode).value?.children.compactMap { child in + courseQuerier.blockWithID(id: child.blockID).value + } ?? [] } - - currentBlock = firstInCompleteBlock + + currentBlock = blocks.first(where: { !$0.isCompleted }) ?? blocks.last } private func updateView() { @@ -224,7 +219,12 @@ class NewCourseContentController: UIViewController, InterfaceOrientationOverridi } override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { - setStatusBar(color: environment.styles.primaryLightColor()) + coordinator.animate { [weak self] _ in + guard let weakSelf = self else { return } + DispatchQueue.main.async { + weakSelf.setStatusBar(color: weakSelf.environment.styles.primaryLightColor()) + } + } } } @@ -235,6 +235,24 @@ extension NewCourseContentController: CourseContentPageViewControllerDelegate { if var controller = controller.viewControllers?.first as? ScrollableDelegateProvider { controller.scrollableDelegate = self } + + // header animation is overlapping with UIPageController animation which results in crash + // calling the header animation after a delay of 1 sec to overcome the issue + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + self?.updateHeaderState(with: controller) + } + } + + private func updateHeaderState(with controller: CourseContentPageViewController) { + if let controller = controller.viewControllers?.first as? VideoBlockViewController { + if currentOrientation() != .portrait { + collapseHeaderView() + } else if headerViewState == .collapsed { + collapseHeaderView() + } else if headerViewState == .expanded { + expandHeaderView() + } + } } } diff --git a/Source/NewCourseDashboardViewController.swift b/Source/NewCourseDashboardViewController.swift index 380170902b..2d90e4f0a2 100644 --- a/Source/NewCourseDashboardViewController.swift +++ b/Source/NewCourseDashboardViewController.swift @@ -64,7 +64,12 @@ class NewCourseDashboardViewController: UIViewController, InterfaceOrientationOv private var error: NSError? private var courseAccessHelper: CourseAccessHelper? private var selectedTabbarItem: TabBarItem? - private var headerViewState: HeaderViewState = .expanded + + private var headerViewState: HeaderViewState = .expanded { + didSet { + headerView.state = headerViewState + } + } private var tabBarItems: [TabBarItem] = [] private var isModalDismissable = true private let courseStream: BackedStream @@ -103,6 +108,16 @@ class NewCourseDashboardViewController: UIViewController, InterfaceOrientationOv environment.analytics.trackScreen(withName: OEXAnalyticsScreenCourseDashboard, courseID: courseID, value: nil) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if headerViewState == .collapsed { + collapseHeaderView() + } else if headerViewState == .expanded { + expandHeaderView() + } + } + override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) navigationController?.setNavigationBarHidden(false, animated: true) @@ -253,7 +268,19 @@ class NewCourseDashboardViewController: UIViewController, InterfaceOrientationOv override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { setupContentView() - setStatusBar(color: environment.styles.primaryLightColor()) + + coordinator.animate { [weak self] _ in + guard let weakSelf = self else { return } + DispatchQueue.main.async { + weakSelf.setStatusBar(color: weakSelf.environment.styles.primaryLightColor()) + } + } + + if headerViewState == .collapsed { + collapseHeaderView() + } else if headerViewState == .expanded { + expandHeaderView() + } } private func prepareTabViewData() { @@ -584,22 +611,27 @@ extension NewCourseDashboardViewController: NewCourseDashboardViewControllerDele public extension UIViewController { func setStatusBar(color: UIColor) { - DispatchQueue.main.async { [weak self] in - let tag = 123454321 - let overView: UIView - if let taggedView = self?.view.viewWithTag(tag) { - overView = taggedView - } - else { - overView = UIView() - overView.tag = tag - self?.view.addSubview(overView) - } - - let height = UIApplication.shared.window?.windowScene?.windows.first?.safeAreaInsets.top ?? 0 - let frame = UIApplication.shared.window?.windowScene?.statusBarManager?.statusBarFrame ?? .zero - overView.frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: height) - overView.backgroundColor = color + let tag = 123454321 + let overView: UIView + if let taggedView = view.viewWithTag(tag) { + overView = taggedView + } + else { + overView = UIView() + overView.tag = tag + view.addSubview(overView) + } + + let height = UIApplication.shared.window?.windowScene?.windows.first?.safeAreaInsets.top ?? 0 + let frame = UIApplication.shared.window?.windowScene?.statusBarManager?.statusBarFrame ?? .zero + overView.frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: height) + overView.backgroundColor = color + } + + func removeStatusBar() { + let tag = 123454321 + if let taggedView = view.viewWithTag(tag) { + taggedView.removeFromSuperview() } } } diff --git a/Source/OEXRouter+Swift.swift b/Source/OEXRouter+Swift.swift index e05df5a30d..64b10e75f9 100644 --- a/Source/OEXRouter+Swift.swift +++ b/Source/OEXRouter+Swift.swift @@ -138,7 +138,7 @@ extension OEXRouter { func showContainerForBlockWithID(blockID: CourseBlockID?, type: CourseBlockDisplayType, parentID: CourseBlockID?, courseID: CourseBlockID, fromController controller: UIViewController, forMode mode: CourseOutlineMode? = .full, completion: ((UIViewController) -> Void)? = nil) { if environment.config.isNewComponentNavigationEnabled { - let contentController = NewCourseContentController(environment: environment, blockID: blockID, parentID: parentID, courseID: courseID) + let contentController = NewCourseContentController(environment: environment, blockID: blockID, parentID: parentID, courseID: courseID, courseOutlineMode: mode) controller.navigationController?.pushViewController(contentController, animated: true, completion: completion) } else { showContainerForBlockWithIDOld(blockID: blockID, type: type, parentID: parentID, courseID: courseID, fromController: controller, forMode: mode, completion: completion) diff --git a/Source/UIViewController+CommonAdditions.swift b/Source/UIViewController+CommonAdditions.swift index 02ac2fa1f1..7ad3ee397a 100644 --- a/Source/UIViewController+CommonAdditions.swift +++ b/Source/UIViewController+CommonAdditions.swift @@ -65,4 +65,14 @@ extension UIViewController { } navigationItem.leftBarButtonItem = backItem } + + func findParentViewController(type: T.Type) -> T? { + if let parentViewController = self.parent as? T { + return parentViewController + } else if let parentViewController = self.parent { + return parentViewController.findParentViewController(type: type) + } else { + return nil + } + } } diff --git a/Source/VideoBlockViewController.swift b/Source/VideoBlockViewController.swift index b4e6d144dd..4a3d812eb2 100644 --- a/Source/VideoBlockViewController.swift +++ b/Source/VideoBlockViewController.swift @@ -31,7 +31,7 @@ class VideoBlockViewController : OfflineSupportViewController, CourseBlockViewCo private var playOverlayButton: UIButton? private var overlayLabel: UILabel? var shouldCelebrationAppear: Bool - + init(environment : Environment, blockID : CourseBlockID?, courseID: String, shouldCelebrationAppear: Bool = false) { self.blockID = blockID self.environment = environment @@ -442,16 +442,29 @@ class VideoBlockViewController : OfflineSupportViewController, CourseBlockViewCo } } - // willTransition only called in case of iPhone because iPhone has regular and compact vertical classes. - // This method is specially for iPad - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - guard UIDevice.current.userInterfaceIdiom == .pad else { return } - - if videoPlayer.isFullScreen { - videoPlayer.setFullscreen(fullscreen: !UIDevice.current.orientation.isPortrait, animated: true, with: currentOrientation(), forceRotate: false) - } - else if UIDevice.current.orientation.isLandscape { - videoPlayer.setFullscreen(fullscreen: true, animated: true, with: currentOrientation(), forceRotate: false) + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + if UIDevice.current.userInterfaceIdiom == .pad { + if videoPlayer.isFullScreen { + videoPlayer.setFullscreen(fullscreen: !UIDevice.current.orientation.isPortrait, animated: true, with: currentOrientation(), forceRotate: false) + } else if UIDevice.current.orientation.isLandscape { + videoPlayer.setFullscreen(fullscreen: true, animated: true, with: currentOrientation(), forceRotate: false) + } + } else { + DispatchQueue.main.async { [weak self] in + if let weakSelf = self { + if weakSelf.chromeCastManager.isMiniPlayerAdded { return } + + if weakSelf.videoPlayer.isFullScreen { + if UITraitCollection.current.verticalSizeClass == .regular { + weakSelf.videoPlayer.setFullscreen(fullscreen: false, animated: true, with: weakSelf.currentOrientation(), forceRotate: false) + } else { + weakSelf.videoPlayer.setFullscreen(fullscreen: true, animated: true, with: weakSelf.currentOrientation(), forceRotate: false) + } + } else if UITraitCollection.current.verticalSizeClass == .compact && !weakSelf.shouldCelebrationAppear { + weakSelf.videoPlayer.setFullscreen(fullscreen: true, animated: true, with: weakSelf.currentOrientation(), forceRotate: false) + } + } + } } }