Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

Commit

Permalink
chore: add scrolling behaviour to redesigned Learn tab (#1729)
Browse files Browse the repository at this point in the history
  • Loading branch information
mumer92 committed Mar 2, 2023
1 parent 364b140 commit 018b2f8
Show file tree
Hide file tree
Showing 33 changed files with 168 additions and 69 deletions.
2 changes: 1 addition & 1 deletion Source/CourseDashboardHeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ protocol CourseDashboardHeaderViewDelegate: AnyObject {
func didTapTabbarItem(at position: Int, tabbarItem: TabBarItem)
}

enum CourseDashboardHeaderViewState {
enum HeaderViewState {
case animating
case expanded
case collapsed
Expand Down
3 changes: 2 additions & 1 deletion Source/CourseDashboardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class CourseDashboardViewController: UITabBarController, InterfaceOrientationOve

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)


navigationController?.setNavigationBarHidden(false, animated: true)
environment.analytics.trackScreen(withName: OEXAnalyticsScreenCourseDashboard, courseID: courseID, value: nil)
}

Expand Down
21 changes: 20 additions & 1 deletion Source/CoursesContainerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ extension CoursesContainerViewControllerDelegate {
func reload() {}
}

class CoursesContainerViewController: UICollectionViewController {
class CoursesContainerViewController: UICollectionViewController, ScrollableDelegateProvider {

enum Context {
case courseCatalog
Expand All @@ -122,6 +122,9 @@ class CoursesContainerViewController: UICollectionViewController {

typealias Environment = NetworkManagerProvider & OEXRouterProvider & OEXConfigProvider & OEXInterfaceProvider & OEXAnalyticsProvider & ServerConfigProvider

weak var scrollableDelegate: ScrollableDelegate?
private var scrollByDragging = false

private let environment : Environment
private let context: Context

Expand Down Expand Up @@ -327,3 +330,19 @@ extension CoursesContainerViewController: UICollectionViewDelegateFlowLayout {
return CGSize(width: widthPerItem, height: heightPerItem)
}
}

extension CoursesContainerViewController {
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
scrollByDragging = true
}

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollByDragging {
scrollableDelegate?.scrollViewDidScroll(scrollView: scrollView)
}
}

override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
scrollByDragging = false
}
}
14 changes: 10 additions & 4 deletions Source/EnrolledCoursesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@ import Foundation

var isActionTakenOnUpgradeSnackBar: Bool = false

class EnrolledCoursesViewController : OfflineSupportViewController, InterfaceOrientationOverriding {
class EnrolledCoursesViewController : OfflineSupportViewController, InterfaceOrientationOverriding, ScrollableDelegateProvider {

typealias Environment = OEXAnalyticsProvider & OEXConfigProvider & DataManagerProvider & NetworkManagerProvider & ReachabilityProvider & OEXRouterProvider & OEXStylesProvider & OEXInterfaceProvider & ServerConfigProvider & OEXSessionProvider

let environment : Environment
private let coursesContainer : CoursesContainerViewController
weak var scrollableDelegate: ScrollableDelegate? {
didSet {
coursesContainer.scrollableDelegate = scrollableDelegate
}
}

let environment: Environment
private let coursesContainer: CoursesContainerViewController
private let loadController = LoadStateViewController()
private let refreshController = PullRefreshController()
private let insetsController = ContentInsetsController()
fileprivate let enrollmentFeed: Feed<[UserCourseEnrollment]?>
private let enrollmentFeed: Feed<[UserCourseEnrollment]?>
private let userPreferencesFeed: Feed<UserPreference?>
var handleBannerOnStart: Bool = false // this will be used to send first call for the banners
lazy var courseUpgradeHelper = CourseUpgradeHelper.shared
Expand Down
73 changes: 31 additions & 42 deletions Source/LearnContainerHeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ protocol LearnContainerHeaderItem {
}

class LearnContainerHeaderView: UIView {
static let height = StandardVerticalMargin * 6.5
static let expandedHeight = StandardVerticalMargin * 10.6
static let collapsedHeight = StandardVerticalMargin * 5.5

weak var delegate: LearnContainerHeaderViewDelegate?

private let container = UIView()
private let dropDownContainer = UIView()
var headerViewState: HeaderViewState = .expanded

private lazy var button: UIButton = {
let button = UIButton()
Expand Down Expand Up @@ -53,16 +53,17 @@ class LearnContainerHeaderView: UIView {
return imageView
}()

private var originalFrame: CGRect = .zero

private let container = UIView()
private let dropDownContainer = UIView()
private let dropDown = DropDown()
private let dropDownBottomOffset: CGFloat = StandardVerticalMargin * 2

private var items: [LearnContainerHeaderItem]

private var shouldShowDropDown: Bool {
return items.count > 1
}

private var items: [LearnContainerHeaderItem]

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Expand All @@ -84,40 +85,37 @@ class LearnContainerHeaderView: UIView {
addSubview(dropDownContainer)
addSubview(container)

container.snp.makeConstraints { make in
container.snp.remakeConstraints { make in
make.top.equalTo(self).offset(-6)
make.bottom.equalTo(self)
make.leading.equalTo(self)
make.trailing.equalTo(self)
make.height.equalTo(LearnContainerHeaderView.expandedHeight)
}

dropDownContainer.snp.makeConstraints { make in
make.top.equalTo(self)
make.bottom.equalTo(self)
dropDownContainer.snp.remakeConstraints { make in
make.centerY.equalTo(label)
make.leading.equalTo(self).offset(StandardHorizontalMargin)
make.trailing.equalTo(self).inset(StandardHorizontalMargin)
}

button.snp.makeConstraints { make in
make.top.equalTo(container)
make.bottom.equalTo(container)
button.snp.remakeConstraints { make in
make.centerY.equalTo(label)
make.leading.equalTo(label)
make.trailing.equalTo(imageView)
}

label.snp.makeConstraints { make in
make.top.equalTo(container)
make.bottom.equalTo(container)
label.snp.remakeConstraints { make in
make.bottom.equalTo(container).inset(StandardVerticalMargin)
make.leading.equalTo(container).offset(StandardHorizontalMargin)
}

imageView.snp.makeConstraints { make in
imageView.snp.remakeConstraints { make in
make.leading.equalTo(label.snp.trailing).offset(StandardHorizontalMargin / 2)
make.centerY.equalTo(label)
}

imageView.isHidden = !shouldShowDropDown
originalFrame = container.frame
}

private func setupDropDown() {
Expand All @@ -128,7 +126,7 @@ class LearnContainerHeaderView: UIView {
selectedTextStyle.alignment = .center

dropDown.accessibilityIdentifier = "LearnContainerHeaderView:drop-down-view"
dropDown.bottomOffset = CGPoint(x: 0, y: LearnContainerHeaderView.height)
dropDown.bottomOffset = CGPoint(x: 0, y: dropDownBottomOffset)
dropDown.direction = .bottom
dropDown.anchorView = dropDownContainer
dropDown.dismissMode = .automatic
Expand All @@ -143,7 +141,7 @@ class LearnContainerHeaderView: UIView {
dropDown.selectionAction = { [weak self] index, _ in
guard let weakSelf = self else { return }
weakSelf.dropDown.selectedRowIndex = index
weakSelf.label.attributedText = weakSelf.largeTextStyle.attributedString(withText: weakSelf.items[index].title)
weakSelf.updateHeaderLabel()
weakSelf.delegate?.didTapOnDropDown(item: weakSelf.items[index])
}
dropDown.willShowAction = { [weak self] in
Expand All @@ -154,6 +152,15 @@ class LearnContainerHeaderView: UIView {
}
}

private func updateHeaderLabel() {
let index = dropDown.indexForSelectedRow ?? 0
if headerViewState == .collapsed {
label.attributedText = smallTextStyle.attributedString(withText: items[index].title)
} else if headerViewState == .expanded {
label.attributedText = largeTextStyle.attributedString(withText: items[index].title)
}
}

func changeHeader(for index: Int) {
dropDown.selectedRowIndex = index
label.attributedText = smallTextStyle.attributedString(withText: items[index].title)
Expand All @@ -163,27 +170,9 @@ class LearnContainerHeaderView: UIView {
dropDown.forceHide()
}

func moveToCenter() {
dropDown.bottomOffset = CGPoint(x: 0, y: 44)
container.frame = CGRect(x: 0, y: 0, width: 180, height: 44)
container.center.x = frame.size.width / 2

if let index = dropDown.indexForSelectedRow {
label.attributedText = smallTextStyle.attributedString(withText: items[index].title)
} else {
label.attributedText = smallTextStyle.attributedString(withText: items[0].title)
}
}

func moveBackOriginalFrame() {
container.frame = originalFrame
dropDown.bottomOffset = CGPoint(x: 0, y: 80)

if let index = dropDown.indexForSelectedRow {
label.attributedText = largeTextStyle.attributedString(withText: items[index].title)
} else {
label.attributedText = largeTextStyle.attributedString(withText: items[0].title)
}
func updateHeaderViewState(collapse: Bool) {
headerViewState = collapse ? .collapsed : .expanded
updateHeaderLabel()
}

private func rotateImageView(clockWise: Bool) {
Expand Down
91 changes: 76 additions & 15 deletions Source/LearnContainerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class LearnContainerViewController: UIViewController {

private let environment: Environment

private var headerViewState: HeaderViewState = .expanded

private lazy var components: [Component] = {
var items: [Component] = []
items.append(.courses)
Expand All @@ -40,11 +42,23 @@ class LearnContainerViewController: UIViewController {
return items
}()

private let container = UIView()
private lazy var headerView = LearnContainerHeaderView(items: components)

private let container = UIView()
private let coursesViewController: EnrolledCoursesViewController
private var programsViewController: ProgramsViewController?
private lazy var coursesViewController: EnrolledCoursesViewController = {
let controller = EnrolledCoursesViewController(environment: environment)
controller.scrollableDelegate = self
return controller
}()

private lazy var programsViewController: ProgramsViewController? = {
if programsEnabled, let programsURL = environment.config.programConfig.programURL {
let controller = ProgramsViewController(environment: environment, programsURL: programsURL)
controller.scrollableDelegate = self
return controller
}
return nil
}()

private var selectedComponent: Component?

Expand All @@ -56,11 +70,6 @@ class LearnContainerViewController: UIViewController {

init(environment: Environment) {
self.environment = environment
self.coursesViewController = EnrolledCoursesViewController(environment: environment)
if environment.config.programConfig.enabled,
let programsURL = environment.config.programConfig.programURL {
self.programsViewController = ProgramsViewController(environment: environment, programsURL: programsURL)
}
super.init(nibName: nil, bundle: nil)
setupViews()
}
Expand All @@ -78,9 +87,15 @@ class LearnContainerViewController: UIViewController {
update(component: .courses)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

navigationItem.setHidesBackButton(true, animated: false)
navigationController?.setNavigationBarHidden(true, animated: true)
}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)

headerView.dimissDropDown()
}

Expand All @@ -93,18 +108,18 @@ class LearnContainerViewController: UIViewController {
view.addSubview(headerView)
view.addSubview(container)

headerView.snp.makeConstraints { make in
make.top.equalTo(view)
headerView.snp.remakeConstraints { make in
make.top.equalTo(safeTop)
make.leading.equalTo(safeLeading)
make.trailing.equalTo(safeTrailing)
make.height.equalTo(LearnContainerHeaderView.height)
make.height.lessThanOrEqualTo(LearnContainerHeaderView.expandedHeight)
}

container.snp.makeConstraints { make in
make.top.equalTo(headerView.snp.bottom)
make.bottom.equalTo(view)
make.leading.equalTo(view)
make.trailing.equalTo(view)
make.bottom.equalTo(safeBottom)
make.leading.equalTo(safeLeading)
make.trailing.equalTo(safeTrailing)
}
}

Expand Down Expand Up @@ -189,6 +204,52 @@ extension LearnContainerViewController: LearnContainerHeaderViewDelegate {
}
}

extension LearnContainerViewController: ScrollableDelegate {
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y <= 0 {
if headerViewState == .collapsed {
headerViewState = .animating
expandHeaderView()
}
} else if headerViewState == .expanded {
headerViewState = .animating
collapseHeaderView()
}
}

private func expandHeaderView() {
headerView.snp.remakeConstraints { make in
make.top.equalTo(safeTop)
make.leading.equalTo(safeLeading)
make.trailing.equalTo(safeTrailing)
make.height.lessThanOrEqualTo(LearnContainerHeaderView.expandedHeight)
}

UIView.animate(withDuration: 0.2) { [weak self] in
self?.headerView.updateHeaderViewState(collapse: false)
self?.view.layoutIfNeeded()
} completion: { [weak self] _ in
self?.headerViewState = .expanded
}
}

private func collapseHeaderView() {
headerView.snp.remakeConstraints { make in
make.top.equalTo(safeTop)
make.leading.equalTo(safeLeading)
make.trailing.equalTo(safeTrailing)
make.height.lessThanOrEqualTo(LearnContainerHeaderView.collapsedHeight)
}

UIView.animate(withDuration: 0.2) { [weak self] in
self?.headerView.updateHeaderViewState(collapse: true)
self?.view.layoutIfNeeded()
} completion: { [weak self] _ in
self?.headerViewState = .collapsed
}
}
}

extension LearnContainerViewController {
func t_switchTo(component: Component) {
if component == .programs {
Expand Down
2 changes: 1 addition & 1 deletion Source/NewCourseDashboardViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class NewCourseDashboardViewController: UIViewController, InterfaceOrientationOv
private var error: NSError?
private var courseAccessError: CourseAccessErrorHelper?
private var selectedTabbarItem: TabBarItem?
private var headerViewState: CourseDashboardHeaderViewState = .expanded
private var headerViewState: HeaderViewState = .expanded

private var isModalDismissable = true
private let courseStream: BackedStream<UserCourseEnrollment>
Expand Down
1 change: 1 addition & 0 deletions Source/OEXRouter+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ extension OEXRouter {
let programDetailsController = ProgramsViewController(environment: environment, programsURL: url, viewType: .detail)
programDetailsController.hidesBottomBarWhenPushed = true
controller.navigationController?.pushViewController(programDetailsController, animated: true)
controller.navigationController?.setNavigationBarHidden(false, animated: true)
}

@objc public func showCourseDetails(from controller: UIViewController, with coursePathID: String, bottomBar: UIView?) {
Expand Down
Loading

0 comments on commit 018b2f8

Please sign in to comment.