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

[iOS] Add upgrade banner in the FC47 Primary Course card view #460 #17

Merged
merged 5 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
30 changes: 23 additions & 7 deletions Core/Core/CourseUpgrade/View/UpgradeCourseView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,22 @@ import Swinject
public enum CourseAccessErrorHelperType {
case isEndDateOld(date: Date)
case startDateError(date: Date?)
case auditExpired(date: Date?, sku: String, courseID: String, pacing: String, screen: CourseUpgradeScreen)
case upgradeable(date: Date?, sku: String, courseID: String, pacing: String, screen: CourseUpgradeScreen)
case auditExpired(
date: Date?,
sku: String,
courseID: String,
pacing: String,
screen: CourseUpgradeScreen,
lmsPrice: Double
)
case upgradeable(
date: Date?,
sku: String,
courseID: String,
pacing: String,
screen: CourseUpgradeScreen,
lmsPrice: Double
)
}

public struct UpgradeCourseView: View {
Expand Down Expand Up @@ -45,8 +59,8 @@ public struct UpgradeCourseView: View {

public var body: some View {
switch type {
case let .upgradeable(date, sku, courseID, pacing, screen),
let .auditExpired(date, sku, courseID, pacing, screen):
case let .upgradeable(date, sku, courseID, pacing, screen, lmsPrice),
let .auditExpired(date, sku, courseID, pacing, screen, lmsPrice):
VStack {
let message = CoreLocalization.CourseUpgrade.View.auditMessage
.replacingOccurrences(
Expand All @@ -57,7 +71,7 @@ public struct UpgradeCourseView: View {
isFindCourseButtonVisible: true,
viewModel: Container.shared.resolve(
UpgradeInfoViewModel.self,
arguments: "", message, sku, courseID, screen, pacing
arguments: "", message, sku, courseID, screen, pacing, lmsPrice
)!,
findAction: {
findAction?()
Expand Down Expand Up @@ -126,7 +140,8 @@ public struct UpgradeCourseView: View {
sku: "some sku",
courseID: "courseID",
pacing: "pacing",
screen: .unknown
screen: .unknown,
lmsPrice: .zero
),
coordinate: .constant(0),
collapsed: .constant(false),
Expand All @@ -141,7 +156,8 @@ public struct UpgradeCourseView: View {
sku: "some sku",
courseID: "courseID",
pacing: "pacing",
screen: .unknown
screen: .unknown,
lmsPrice: .zero
),
coordinate: .constant(0),
collapsed: .constant(false),
Expand Down
29 changes: 28 additions & 1 deletion Core/Core/Data/Model/Data_PrimaryEnrollment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,29 @@ public extension DataLayer {
case courseAssignments = "course_assignments"
}

public var sku: String? {
let mode = courseModes?.first { $0.slug == .verified }
return mode?.iosSku
}
public var lmsPrice: Double? {
let mode = courseModes?.first { $0.slug == .verified }
return mode?.lmsPrice
}

var isUpgradeable: Bool {
guard let start = course?.start,
let upgradeDeadline = course?.dynamicUpgradeDeadline,
mode == "audit"
else { return false }

let startDate = Date(iso8601: start)
let dynamicUpgradeDeadline = Date(iso8601: upgradeDeadline)

return startDate.isInPast()
&& sku?.isEmpty == false
&& !dynamicUpgradeDeadline.isInPast()
}

public init(
auditAccessExpires: Date?,
created: String?,
Expand Down Expand Up @@ -216,7 +239,11 @@ public extension DataLayer.PrimaryEnrollment {
resumeTitle: primary.courseStatus?.lastVisitedUnitDisplayName,
auditAccessExpires: primary.auditAccessExpires,
startDisplay: primary.course?.startDisplay.flatMap { Date(iso8601: $0) },
startType: DisplayStartType(value: primary.course?.startType.rawValue)
startType: DisplayStartType(value: primary.course?.startType.rawValue),
isUpgradeable: primary.isUpgradeable,
sku: primary.sku,
lmsPrice: primary.lmsPrice,
isSelfPaced: primary.course?.isSelfPaced ?? false
)
}

Expand Down
15 changes: 13 additions & 2 deletions Core/Core/Domain/Model/PrimaryEnrollment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ public struct PrimaryCourse: Hashable {
public let auditAccessExpires: Date?
public let startDisplay: Date?
public let startType: DisplayStartType?

public let isUpgradeable: Bool
public let sku: String?
public let lmsPrice: Double?
public let isSelfPaced: Bool
public init(
name: String,
org: String,
Expand All @@ -66,7 +69,11 @@ public struct PrimaryCourse: Hashable {
resumeTitle: String?,
auditAccessExpires: Date?,
startDisplay: Date?,
startType: DisplayStartType?
startType: DisplayStartType?,
isUpgradeable: Bool,
sku: String?,
lmsPrice: Double?,
isSelfPaced: Bool
) {
self.name = name
self.org = org
Expand All @@ -84,6 +91,10 @@ public struct PrimaryCourse: Hashable {
self.auditAccessExpires = auditAccessExpires
self.startDisplay = startDisplay
self.startType = startType
self.isUpgradeable = isUpgradeable
self.sku = sku
self.lmsPrice = lmsPrice
self.isSelfPaced = isSelfPaced
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,8 @@ public class CourseContainerViewModel: BaseCourseViewModel {
sku: courseStructure.sku ?? "",
courseID: courseID,
pacing: courseStructure.isSelfPaced ? Pacing.selfPace.rawValue : Pacing.instructor.rawValue,
screen: .courseDashboard
screen: .courseDashboard,
lmsPrice: courseStructure.lmsPrice ?? .zero
)
} else {
return .isEndDateOld(date: courseEnd)
Expand All @@ -351,7 +352,8 @@ public class CourseContainerViewModel: BaseCourseViewModel {
sku: courseStructure.sku ?? "",
courseID: courseID,
pacing: courseStructure.isSelfPaced ? Pacing.selfPace.rawValue : Pacing.instructor.rawValue,
screen: .courseDashboard
screen: .courseDashboard,
lmsPrice: courseStructure.lmsPrice ?? .zero
)

default:
Expand Down
6 changes: 5 additions & 1 deletion Dashboard/Dashboard/Data/DashboardRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,11 @@ class DashboardRepositoryMock: DashboardRepositoryProtocol {
resumeTitle: nil,
auditAccessExpires: nil,
startDisplay: nil,
startType: .unknown
startType: .unknown,
isUpgradeable: false,
sku: nil,
lmsPrice: nil,
isSelfPaced: false
)
return PrimaryEnrollment(primaryCourse: primaryCourse, courses: courses, totalPages: 1, count: 1)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22758" systemVersion="23E224" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="CDAssignment" representedClassName=".CDAssignment" syncable="YES" codeGenerationType="class">
<attribute name="complete" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="date" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
Expand Down Expand Up @@ -67,12 +67,16 @@
<attribute name="courseID" optional="YES" attributeType="String"/>
<attribute name="courseStart" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="hasAccess" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="isSelfPaced" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="isUpgradeable" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="lastVisitedBlockID" optional="YES" attributeType="String"/>
<attribute name="lmsPrice" optional="YES" attributeType="Double" usesScalarValueType="NO"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="org" optional="YES" attributeType="String"/>
<attribute name="progressEarned" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="progressPossible" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="resumeTitle" optional="YES" attributeType="String"/>
<attribute name="sku" optional="YES" attributeType="String"/>
<attribute name="startDisplay" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="startType" optional="YES" attributeType="String"/>
<relationship name="futureAssignments" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="CDAssignment"/>
Expand Down
23 changes: 21 additions & 2 deletions Dashboard/Dashboard/Presentation/Elements/PrimaryCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public struct PrimaryCardView: View {
private var assignmentAction: (String?) -> Void
private var openCourseAction: () -> Void
private var resumeAction: () -> Void
private var upgradeAction: () -> Void
private var isUpgradeable: Bool

public init(
courseName: String,
Expand All @@ -47,7 +49,9 @@ public struct PrimaryCardView: View {
startType: DisplayStartType?,
assignmentAction: @escaping (String?) -> Void,
openCourseAction: @escaping () -> Void,
resumeAction: @escaping () -> Void
resumeAction: @escaping () -> Void,
isUpgradeable: Bool,
upgradeAction: @escaping () -> Void
) {
self.courseName = courseName
self.org = org
Expand All @@ -66,6 +70,8 @@ public struct PrimaryCardView: View {
self.auditAccessExpires = auditAccessExpires
self.startDisplay = startDisplay
self.startType = startType
self.isUpgradeable = isUpgradeable
self.upgradeAction = upgradeAction
}

public var body: some View {
Expand Down Expand Up @@ -147,6 +153,17 @@ public struct PrimaryCardView: View {
}
}

// Upgrade button
if isUpgradeable {
courseButton(
title: CoreLocalization.CourseUpgrade.Button.upgrade,
description: nil,
icon: Image(systemName: "trophy.fill"),
selected: false,
action: upgradeAction
)
}

// ResumeButton
if canResume {
courseButton(
Expand Down Expand Up @@ -282,7 +299,9 @@ struct PrimaryCardView_Previews: PreviewProvider {
startType: .unknown,
assignmentAction: {_ in },
openCourseAction: {},
resumeAction: {}
resumeAction: {},
isUpgradeable: false,
upgradeAction: {}
)
.loadFonts()
}
Expand Down
14 changes: 14 additions & 0 deletions Dashboard/Dashboard/Presentation/PrimaryCourseDashboardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ public struct PrimaryCourseDashboardView<ProgramView: View>: View {
showDates: false,
lastVisitedBlockID: primary.lastVisitedBlockID
)
},
isUpgradeable: primary.isUpgradeable,
upgradeAction: {
Task {@MainActor in
await self.router.showUpgradeInfo(
productName: primary.name,
message: "",
sku: primary.sku ?? "",
courseID: primary.courseID,
screen: .dashboard,
pacing: primary.isSelfPaced ? Pacing.selfPace.rawValue : Pacing.instructor.rawValue,
lmsPrice: primary.lmsPrice ?? .zero
)
}
}
)
}
Expand Down
21 changes: 18 additions & 3 deletions OpenEdX/Data/DashboardPersistence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ public class DashboardPersistence: DashboardPersistenceProtocol {
resumeTitle: cdPrimaryCourse.resumeTitle,
auditAccessExpires: cdPrimaryCourse.auditAccessExpires,
startDisplay: cdPrimaryCourse.startDisplay,
startType: DisplayStartType(value: cdPrimaryCourse.startType)
startType: DisplayStartType(value: cdPrimaryCourse.startType),
isUpgradeable: cdPrimaryCourse.isUpgradeable,
sku: cdPrimaryCourse.sku,
lmsPrice: cdPrimaryCourse.lmsPrice?.doubleValue,
isSelfPaced: cdPrimaryCourse.isSelfPaced
)
}

Expand Down Expand Up @@ -269,7 +273,11 @@ public class DashboardPersistence: DashboardPersistenceProtocol {
resumeTitle: cdPrimaryCourse.resumeTitle,
auditAccessExpires: cdPrimaryCourse.auditAccessExpires,
startDisplay: cdPrimaryCourse.startDisplay,
startType: DisplayStartType(value: cdPrimaryCourse.startType)
startType: DisplayStartType(value: cdPrimaryCourse.startType),
isUpgradeable: cdPrimaryCourse.isUpgradeable,
sku: cdPrimaryCourse.sku,
lmsPrice: cdPrimaryCourse.lmsPrice?.doubleValue,
isSelfPaced: cdPrimaryCourse.isSelfPaced
)
}

Expand Down Expand Up @@ -389,7 +397,10 @@ public class DashboardPersistence: DashboardPersistenceProtocol {
return cdAssignment
}
cdPrimaryCourse.pastAssignments = NSSet(array: pastAssignments)

var lmsPrice: NSNumber?
if let price = primaryCourse.lmsPrice {
lmsPrice = NSNumber(value: price)
}
cdPrimaryCourse.name = primaryCourse.name
cdPrimaryCourse.org = primaryCourse.org
cdPrimaryCourse.courseID = primaryCourse.courseID
Expand All @@ -401,6 +412,10 @@ public class DashboardPersistence: DashboardPersistenceProtocol {
cdPrimaryCourse.progressPossible = Int32(primaryCourse.progressPossible)
cdPrimaryCourse.lastVisitedBlockID = primaryCourse.lastVisitedBlockID
cdPrimaryCourse.resumeTitle = primaryCourse.resumeTitle
cdPrimaryCourse.sku = primaryCourse.sku
cdPrimaryCourse.lmsPrice = lmsPrice
cdPrimaryCourse.isUpgradeable = primaryCourse.isUpgradeable
cdPrimaryCourse.isSelfPaced = primaryCourse.isSelfPaced ?? false

newEnrollment.primaryCourse = cdPrimaryCourse
}
Expand Down
Loading