From 5e53bdae1a950628f12d9c17bfb2fdedf937c8c5 Mon Sep 17 00:00:00 2001 From: Muhammad Umer Date: Fri, 17 Mar 2023 16:19:28 +0500 Subject: [PATCH] chore: resume course button implementation for new design #1737 (#1738) --- Source/CourseOutlineHeaderCell.swift | 2 + Source/CourseOutlineTableSource.swift | 178 ++++++++++++------ Source/CourseSectionTableViewCell.swift | 4 + Source/Icon.swift | 3 + Source/NewCourseDashboardViewController.swift | 3 - Source/ResumeCourseHeaderView.swift | 68 +++++++ Source/ar.lproj/Localizable-2.strings | 2 + Source/de.lproj/Localizable-2.strings | 2 + Source/en.lproj/Localizable-2.strings | 2 + Source/es-419.lproj/Localizable-2.strings | 2 + Source/fr.lproj/Localizable-2.strings | 2 + Source/he.lproj/Localizable-2.strings | 2 + Source/ja.lproj/Localizable-2.strings | 2 + Source/pt-BR.lproj/Localizable-2.strings | 2 + Source/tr.lproj/Localizable-2.strings | 2 + Source/vi.lproj/Localizable-2.strings | 2 + Source/zh-Hans.lproj/Localizable-2.strings | 2 + edX.xcodeproj/project.pbxproj | 4 + 18 files changed, 220 insertions(+), 64 deletions(-) create mode 100644 Source/ResumeCourseHeaderView.swift diff --git a/Source/CourseOutlineHeaderCell.swift b/Source/CourseOutlineHeaderCell.swift index 3ef90678f8..5e7ce7a0ea 100644 --- a/Source/CourseOutlineHeaderCell.swift +++ b/Source/CourseOutlineHeaderCell.swift @@ -107,6 +107,8 @@ class CourseOutlineHeaderCell: UITableViewHeaderFooterView { containerView.addSubview(button) containerView.addSubview(trailingImageView) button.superview?.bringSubviewToFront(button) + + containerView.backgroundColor = OEXStyles.shared().neutralWhiteT() } func setConstraintsForNewDesign() { diff --git a/Source/CourseOutlineTableSource.swift b/Source/CourseOutlineTableSource.swift index 90b97a54d7..d6969558fc 100644 --- a/Source/CourseOutlineTableSource.swift +++ b/Source/CourseOutlineTableSource.swift @@ -38,12 +38,14 @@ class CourseOutlineTableController: UITableViewController, ScrollableDelegatePro private let courseCard = CourseCardView(frame: .zero) private var courseCertificateView: CourseCertificateView? private let headerContainer = UIView() + + private lazy var resumeCourseHeaderView = ResumeCourseHeaderView() private lazy var resumeCourseView = CourseOutlineHeaderView(frame: .zero, styles: OEXStyles.shared(), titleText: Strings.resume, subtitleText: "Placeholder") private lazy var valuePropView = UIView() var courseVideosHeaderView: CourseVideosHeaderView? let refreshController = PullRefreshController() - + private var isResumeCourse = false private var shouldHideTableViewHeader: Bool = false @@ -60,7 +62,7 @@ class CourseOutlineTableController: UITableViewController, ScrollableDelegatePro var isSectionOutline = false { didSet { - if isSectionOutline || OEXConfig.shared().isNewDashboardEnabled { + if isSectionOutline || environment.config.isNewDashboardEnabled { hideTableHeaderView() } @@ -127,13 +129,78 @@ class CourseOutlineTableController: UITableViewController, ScrollableDelegatePro tableView.register(CourseUnknownTableViewCell.self, forCellReuseIdentifier: CourseUnknownTableViewCell.identifier) tableView.register(CourseSectionTableViewCell.self, forCellReuseIdentifier: CourseSectionTableViewCell.identifier) tableView.register(DiscussionTableViewCell.self, forCellReuseIdentifier: DiscussionTableViewCell.identifier) - configureHeaderView() + + if !environment.config.isNewDashboardEnabled || courseOutlineMode != .full { + configureOldHeaderView() + } + refreshController.setupInScrollView(scrollView: tableView) - setAccessibilityIdentifiers() + } + + private func configureNewHeaderView() { + headerContainer.addSubview(resumeCourseHeaderView) + tableView.tableHeaderView = headerContainer - if OEXConfig.shared().isNewDashboardEnabled { - hideTableHeaderView() + resumeCourseHeaderView.snp.makeConstraints { make in + make.top.equalTo(headerContainer) + make.bottom.equalTo(headerContainer).inset(StandardVerticalMargin * 2) + make.leading.equalTo(headerContainer).offset(StandardHorizontalMargin) + make.trailing.equalTo(headerContainer).inset(StandardHorizontalMargin) + make.height.equalTo(StandardVerticalMargin * 5) + } + + tableView.setAndLayoutTableHeaderView(header: headerContainer) + + UIView.animate(withDuration: 0.1) { [weak self] in + self?.view.layoutIfNeeded() + } + } + + private func configureOldHeaderView() { + if courseOutlineMode == .full { + courseDateBannerView.delegate = self + headerContainer.addSubview(courseDateBannerView) + headerContainer.addSubview(courseCard) + headerContainer.addSubview(resumeCourseView) + addCertificateView() + addValuePropView() + + courseDateBannerView.snp.remakeConstraints { make in + make.trailing.equalTo(headerContainer) + make.leading.equalTo(headerContainer) + make.top.equalTo(headerContainer) + make.height.equalTo(0) + } + } + if let course = enrollment?.course { + switch courseOutlineMode { + case .full: + CourseCardViewModel.onCourseOutline(course: course).apply(card: courseCard, networkManager: environment.networkManager) + break + case .video: + if let courseBlockID = courseBlockID { + let stream = courseQuerier.supportedBlockVideos(forCourseID: courseID, blockID: courseBlockID) + stream.listen(self) {[weak self] downloads in + self?.videos = downloads.value?.filter { $0.summary?.isDownloadableVideo ?? false } + self?.addBulkDownloadHeaderView(course: course, videos: self?.videos) + } + } + else { + videos = environment.interface?.downloadableVideos(of: course) + addBulkDownloadHeaderView(course: course, videos: videos) + } + break + } + refreshTableHeaderView(isResumeCourse: false) + } + } + + private func addBulkDownloadHeaderView(course: OEXCourse, videos: [OEXHelperVideoDownload]?) { + courseVideosHeaderView = CourseVideosHeaderView(with: course, environment: environment, videos: videos, blockID: courseBlockID) + courseVideosHeaderView?.delegate = self + if let headerView = courseVideosHeaderView { + headerContainer.addSubview(headerView) } } @@ -162,7 +229,7 @@ class CourseOutlineTableController: UITableViewController, ScrollableDelegatePro } private func shouldApplyNewStyle(_ group: CourseOutlineQuerier.BlockGroup) -> Bool { - return OEXConfig.shared().isNewDashboardEnabled && group.block.type == .Chapter && courseOutlineMode == .full + return environment.config.isNewDashboardEnabled && group.block.type == .Chapter && courseOutlineMode == .full } // MARK: UITableView DataSource & Delegate @@ -345,55 +412,6 @@ extension CourseOutlineTableController { } } - private func configureHeaderView() { - if courseOutlineMode == .full { - courseDateBannerView.delegate = self - headerContainer.addSubview(courseDateBannerView) - headerContainer.addSubview(courseCard) - headerContainer.addSubview(resumeCourseView) - addCertificateView() - addValuePropView() - - courseDateBannerView.snp.remakeConstraints { make in - make.trailing.equalTo(headerContainer) - make.leading.equalTo(headerContainer) - make.top.equalTo(headerContainer) - make.height.equalTo(0) - } - } - if let course = enrollment?.course { - switch courseOutlineMode { - case .full: - CourseCardViewModel.onCourseOutline(course: course).apply(card: courseCard, networkManager: environment.networkManager) - break - case .video: - if let courseBlockID = courseBlockID { - let stream = courseQuerier.supportedBlockVideos(forCourseID: courseID, blockID: courseBlockID) - stream.listen(self) {[weak self] downloads in - self?.videos = downloads.value?.filter { $0.summary?.isDownloadableVideo ?? false } - self?.addBulkDownloadHeaderView(course: course, videos: self?.videos) - } - } - else { - videos = environment.interface?.downloadableVideos(of: course) - addBulkDownloadHeaderView(course: course, videos: videos) - } - break - } - refreshTableHeaderView(isResumeCourse: false) - } - } - - private func addBulkDownloadHeaderView(course: OEXCourse, videos: [OEXHelperVideoDownload]?) { - courseVideosHeaderView = CourseVideosHeaderView(with: course, environment: environment, videos: videos, blockID: courseBlockID) - courseVideosHeaderView?.delegate = self - if let headerView = courseVideosHeaderView { - headerContainer.addSubview(headerView) - } - - refreshTableHeaderView(isResumeCourse: false) - } - private func allBlocksCompleted(for group: CourseOutlineQuerier.BlockGroup) -> Bool { if courseOutlineMode == .video { return group.block.type == .Unit ? @@ -466,16 +484,37 @@ extension CourseOutlineTableController { /// Shows the last accessed Header from the item as argument. Also, sets the relevant action if the course block exists in the course outline. func showResumeCourse(item: ResumeCourseItem) { + if environment.config.isNewDashboardEnabled { + showResumeCourseNewDesign(item: item) + } else { + showResumeCourseOldDesign(item: item) + } + } + + func showResumeCourseNewDesign(item: ResumeCourseItem) { + if !item.lastVisitedBlockID.isEmpty { + courseQuerier.blockWithID(id: item.lastVisitedBlockID).extendLifetimeUntilFirstResult { [weak self] block in + self?.configureNewHeaderView() + self?.resumeCourseHeaderView.tapAction = { [weak self] in + self?.resumeCourse(with: item) + } + } failure: { [weak self] _ in + self?.tableView.tableHeaderView = nil + } + } + } + + func showResumeCourseOldDesign(item: ResumeCourseItem) { if !item.lastVisitedBlockID.isEmpty { - courseQuerier.blockWithID(id: item.lastVisitedBlockID).extendLifetimeUntilFirstResult (success: { [weak self] block in + courseQuerier.blockWithID(id: item.lastVisitedBlockID).extendLifetimeUntilFirstResult { [weak self] block in self?.resumeCourseView.subtitleText = block.displayName self?.resumeCourseView.setViewButtonAction { [weak self] _ in self?.resumeCourse(with: item) } self?.refreshTableHeaderView(isResumeCourse: true) - }, failure: { [weak self] _ in + } failure: { [weak self] _ in self?.refreshTableHeaderView(isResumeCourse: false) - }) + } } else { refreshTableHeaderView(isResumeCourse: false) } @@ -491,11 +530,13 @@ extension CourseOutlineTableController { } func showCourseDateBanner(bannerInfo: DatesBannerInfo) { + if environment.config.isNewDashboardEnabled { return } courseDateBannerView.bannerInfo = bannerInfo updateCourseDateBannerView(show: true) } func hideCourseDateBanner() { + if environment.config.isNewDashboardEnabled { return } courseDateBannerView.bannerInfo = nil updateCourseDateBannerView(show: false) } @@ -532,7 +573,21 @@ extension CourseOutlineTableController { tableView.tableHeaderView = nil return } + + if environment.config.isNewDashboardEnabled { + updateNewHeaderConstraints() + } else { + updateOldHeaderConstraints() + } + tableView.setAndLayoutTableHeaderView(header: headerContainer) + } + + private func updateNewHeaderConstraints() { + + } + + private func updateOldHeaderConstraints() { var constraintView: UIView = courseCard courseCard.snp.remakeConstraints { make in make.trailing.equalTo(headerContainer) @@ -579,10 +634,11 @@ extension CourseOutlineTableController { make.height.equalTo(height) make.bottom.equalTo(headerContainer) } - tableView.setAndLayoutTableHeaderView(header: headerContainer) } private func refreshTableHeaderView(isResumeCourse: Bool) { + if environment.config.isNewDashboardEnabled && courseOutlineMode == .full { return } + self.isResumeCourse = isResumeCourse resumeCourseView.isHidden = !isResumeCourse @@ -709,7 +765,7 @@ extension CourseOutlineTableController { extension CourseOutlineTableController: CourseOutlineHeaderCellDelegate { func toggleSection(section: Int) { - if OEXConfig.shared().isNewDashboardEnabled { + if environment.config.isNewDashboardEnabled { collapsedSections = collapsedSections.symmetricDifference([section]) tableView.reloadSections([section], with: .none) } diff --git a/Source/CourseSectionTableViewCell.swift b/Source/CourseSectionTableViewCell.swift index 25832c1ce4..dc530ad09e 100644 --- a/Source/CourseSectionTableViewCell.swift +++ b/Source/CourseSectionTableViewCell.swift @@ -73,6 +73,10 @@ class CourseSectionTableViewCell: SwipeableCell, CourseBlockContainerCell { } downloadView.addGestureRecognizer(tapGesture) setAccessibilityIdentifiers() + + if OEXConfig.shared().isNewDashboardEnabled { + content.backgroundColor = OEXStyles.shared().neutralWhiteT() + } } private func setAccessibilityIdentifiers() { diff --git a/Source/Icon.swift b/Source/Icon.swift index 04c680f5e5..450b920f05 100644 --- a/Source/Icon.swift +++ b/Source/Icon.swift @@ -91,6 +91,7 @@ public enum Icon { case Announcements case ArrowUp case ArrowDown + case ArrowForward case Camera case ChevronRight case Close @@ -179,6 +180,8 @@ public enum Icon { return MaterialIconRenderer(icon: .arrowUpward) case .ArrowDown: return MaterialIconRenderer(icon: .arrowDownward) + case .ArrowForward: + return MaterialIconRenderer(icon: .arrowForward) case .Account: return MaterialIconRenderer(icon: .moreVert) case .Camera: diff --git a/Source/NewCourseDashboardViewController.swift b/Source/NewCourseDashboardViewController.swift index a7df778897..22dc548e77 100644 --- a/Source/NewCourseDashboardViewController.swift +++ b/Source/NewCourseDashboardViewController.swift @@ -31,9 +31,6 @@ class NewCourseDashboardViewController: UIViewController, InterfaceOrientationOv return view }() - private var collapsed = false - private var isAnimating = false - private lazy var courseUpgradeHelper = CourseUpgradeHelper.shared private var pacing: String { diff --git a/Source/ResumeCourseHeaderView.swift b/Source/ResumeCourseHeaderView.swift new file mode 100644 index 0000000000..b1dd7eb897 --- /dev/null +++ b/Source/ResumeCourseHeaderView.swift @@ -0,0 +1,68 @@ +// +// ResumeCourseHeaderView.swift +// edX +// +// Created by MuhammadUmer on 12/03/2023. +// Copyright © 2023 edX. All rights reserved. +// + +import UIKit + +class ResumeCourseHeaderView: UIView { + + var tapAction: (() -> ())? + + private lazy var button: UIButton = { + let button = UIButton() + + let arrowImage = Icon.ArrowForward.imageWithFontSize(size: 18).image(with: OEXStyles.shared().primaryBaseColor()) + let imageAttachment = NSTextAttachment() + imageAttachment.image = arrowImage + + if let image = imageAttachment.image { + imageAttachment.bounds = CGRect(x: 0, y: -4, width: image.size.width, height: image.size.height) + } + + let attributedImageString = NSAttributedString(attachment: imageAttachment) + let style = OEXTextStyle(weight: .bold, size: .base, color: OEXStyles.shared().primaryBaseColor()) + + let attributedStrings = [ + style.attributedString(withText: Strings.Dashboard.resumeCourse), + NSAttributedString(string: " "), + attributedImageString, + ] + + let attributedTitle = NSAttributedString.joinInNaturalLayout(attributedStrings: attributedStrings) + + button.setAttributedTitle(attributedTitle, for: UIControl.State()) + button.backgroundColor = OEXStyles.shared().neutralWhiteT() + button.layer.borderWidth = 1 + button.layer.borderColor = OEXStyles.shared().neutralXLight().cgColor + button.layer.cornerRadius = 0 + button.layer.masksToBounds = true + button.oex_addAction({ [weak self] _ in + self?.tapAction?() + }, for: .touchUpInside) + return button + }() + + init() { + super.init(frame: .zero) + setupViews() + setConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupViews() { + addSubview(button) + } + + private func setConstraints() { + button.snp.makeConstraints { make in + make.edges.equalTo(self) + } + } +} diff --git a/Source/ar.lproj/Localizable-2.strings b/Source/ar.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/ar.lproj/Localizable-2.strings +++ b/Source/ar.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/de.lproj/Localizable-2.strings b/Source/de.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/de.lproj/Localizable-2.strings +++ b/Source/de.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/en.lproj/Localizable-2.strings b/Source/en.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/en.lproj/Localizable-2.strings +++ b/Source/en.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/es-419.lproj/Localizable-2.strings b/Source/es-419.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/es-419.lproj/Localizable-2.strings +++ b/Source/es-419.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/fr.lproj/Localizable-2.strings b/Source/fr.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/fr.lproj/Localizable-2.strings +++ b/Source/fr.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/he.lproj/Localizable-2.strings b/Source/he.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/he.lproj/Localizable-2.strings +++ b/Source/he.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/ja.lproj/Localizable-2.strings b/Source/ja.lproj/Localizable-2.strings index 696d728e63..b94ba04df5 100644 --- a/Source/ja.lproj/Localizable-2.strings +++ b/Source/ja.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/pt-BR.lproj/Localizable-2.strings b/Source/pt-BR.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/pt-BR.lproj/Localizable-2.strings +++ b/Source/pt-BR.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/tr.lproj/Localizable-2.strings b/Source/tr.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/tr.lproj/Localizable-2.strings +++ b/Source/tr.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/vi.lproj/Localizable-2.strings b/Source/vi.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/vi.lproj/Localizable-2.strings +++ b/Source/vi.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/Source/zh-Hans.lproj/Localizable-2.strings b/Source/zh-Hans.lproj/Localizable-2.strings index 73a0e2b2c8..3aef5e0379 100644 --- a/Source/zh-Hans.lproj/Localizable-2.strings +++ b/Source/zh-Hans.lproj/Localizable-2.strings @@ -57,3 +57,5 @@ "DASHBOARD.GENERAL_ERROR_MESSAGE"="An error occured while loading your courses"; /* Enrolled courses loading error try again*/ "DASHBOARD.TRY_AGAIN"="Try again"; +/* Course Dashboard Resume Course button title*/ +"DASHBOARD.RESUME_COURSE"="Resume course"; diff --git a/edX.xcodeproj/project.pbxproj b/edX.xcodeproj/project.pbxproj index beb0a5554d..afc7b4be25 100644 --- a/edX.xcodeproj/project.pbxproj +++ b/edX.xcodeproj/project.pbxproj @@ -219,6 +219,7 @@ 5F903AF4255020D7006365DE /* UIDeviceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F903AF3255020D7006365DE /* UIDeviceExtension.swift */; }; 5F99F3BD26C68308000605B4 /* VideoDownloadQualityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F99F3BC26C68308000605B4 /* VideoDownloadQualityViewController.swift */; }; 5FA4266F2966B1110013BBA8 /* CourseAccessHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA4266E2966B1110013BBA8 /* CourseAccessHelper.swift */; }; + 5F9EE5E929BE4BF300FF5A0A /* ResumeCourseHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F9EE5E829BE4BF300FF5A0A /* ResumeCourseHeaderView.swift */; }; 5FA7FEB1266A2BE5006286B6 /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA7FEB0266A2BE5006286B6 /* BrowserViewController.swift */; }; 5FA8F8D827C4B153006003DA /* ShimmerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FA8F8D327C4B153006003DA /* ShimmerView.swift */; }; 5FAE055324EA647D00A29B1D /* CourseDates.json in Resources */ = {isa = PBXBuildFile; fileRef = 5FAE055124EA618000A29B1D /* CourseDates.json */; }; @@ -1232,6 +1233,7 @@ 5F903AF3255020D7006365DE /* UIDeviceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDeviceExtension.swift; sourceTree = ""; }; 5F99F3BC26C68308000605B4 /* VideoDownloadQualityViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDownloadQualityViewController.swift; sourceTree = ""; }; 5FA4266E2966B1110013BBA8 /* CourseAccessHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseAccessHelper.swift; sourceTree = ""; }; + 5F9EE5E829BE4BF300FF5A0A /* ResumeCourseHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResumeCourseHeaderView.swift; sourceTree = ""; }; 5FA7FEB0266A2BE5006286B6 /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = ""; }; 5FA8F8D327C4B153006003DA /* ShimmerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShimmerView.swift; sourceTree = ""; }; 5FAE055124EA618000A29B1D /* CourseDates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = CourseDates.json; sourceTree = ""; }; @@ -3661,6 +3663,7 @@ E0249F2A26305486007F9AE1 /* OpenInExternalBrowserView.swift */, 7772BE721AF2FECC0081CA7A /* CourseOutlineHeaderCell.swift */, 773A047F1AF2E6DA0076532C /* CourseOutlineTableSource.swift */, + 5F9EE5E829BE4BF300FF5A0A /* ResumeCourseHeaderView.swift */, 773A04771AF2D5DE0076532C /* CourseOutlineViewController.swift */, 46CECC3B1B041D270073C63A /* AdditionalTableViewCell.swift */, 1AE0A6C71BF4F68A00E14917 /* CourseCertificateCell.swift */, @@ -5321,6 +5324,7 @@ 775434831AD7394D00635A40 /* OEXPushNotificationManager.m in Sources */, 7793764F1BED404C00900A8C /* OEXConfig+URLCredentialProvider.swift in Sources */, E0060349265650A400CEBB12 /* BrazeListener.swift in Sources */, + 5F9EE5E929BE4BF300FF5A0A /* ResumeCourseHeaderView.swift in Sources */, 77E648591C91235A00B6740D /* OEXConfig+AppFeatures.swift in Sources */, 2254EF03207610FF00BA183C /* CustomPlayerButton.swift in Sources */, 77E00B711B82A06F00573622 /* GrowingTextViewController.swift in Sources */,