Skip to content
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
8 changes: 4 additions & 4 deletions Stepic.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@
62E98CE4B58258211CADF340 /* ExploreBlockHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98AA14C7255A86152B53B /* ExploreBlockHeaderView.swift */; };
62E98CF698823997CBF8E919 /* CourseListViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E980F30C9CD714BC60ED0D /* CourseListViewDelegate.swift */; };
62E98D0458F64EA380FC2B2F /* BaseQuizDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98FA8FCC70EA92872D541 /* BaseQuizDataFlow.swift */; };
62E98D14FE058C5D8D39D63F /* CourseWidgetButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98AA2D2D17D34EBAEC4AA /* CourseWidgetButton.swift */; };
62E98D14FE058C5D8D39D63F /* CourseWidgetContinueLearningButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98AA2D2D17D34EBAEC4AA /* CourseWidgetContinueLearningButton.swift */; };
62E98D1BBD212BF163DCAF48 /* SectionsPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9867D3CF2B4F6A52B2B4A /* SectionsPersistenceService.swift */; };
62E98D1D1B374D09B4E6EAA5 /* LessonsPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E985EA7905BE734BB77FBD /* LessonsPersistenceService.swift */; };
62E98D1F80A78DAB75D7D8D0 /* FullscreenCourseListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98A1A76183E4780F9343C /* FullscreenCourseListViewController.swift */; };
Expand Down Expand Up @@ -2186,7 +2186,7 @@
62E98A81E414B0314D76BC70 /* WriteCommentSolutionControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WriteCommentSolutionControl.swift; sourceTree = "<group>"; };
62E98A9C4517D3C76CA66D49 /* CourseInfoTabInfoHeaderBlockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseInfoTabInfoHeaderBlockView.swift; sourceTree = "<group>"; };
62E98AA14C7255A86152B53B /* ExploreBlockHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExploreBlockHeaderView.swift; sourceTree = "<group>"; };
62E98AA2D2D17D34EBAEC4AA /* CourseWidgetButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseWidgetButton.swift; sourceTree = "<group>"; };
62E98AA2D2D17D34EBAEC4AA /* CourseWidgetContinueLearningButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseWidgetContinueLearningButton.swift; sourceTree = "<group>"; };
62E98AA5B57A1D9799E79B0B /* NewMatchingQuizViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewMatchingQuizViewModel.swift; sourceTree = "<group>"; };
62E98ABA6ADA2DEA8E21746B /* CourseInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseInfoView.swift; sourceTree = "<group>"; };
62E98AC188792B6E7B8EABC9 /* CourseListCollectionOutputProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseListCollectionOutputProtocol.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -5816,7 +5816,7 @@
62E98B9687B645AE04CB6B5D /* Widget */ = {
isa = PBXGroup;
children = (
62E98AA2D2D17D34EBAEC4AA /* CourseWidgetButton.swift */,
62E98AA2D2D17D34EBAEC4AA /* CourseWidgetContinueLearningButton.swift */,
62E98A35E88BBE09B6D20E70 /* CourseWidgetCoverView.swift */,
62E986B68558033E8B3DDF6A /* CourseWidgetLabel.swift */,
62E98E718321FFBD61C5A671 /* CourseWidgetStatsItemView.swift */,
Expand Down Expand Up @@ -7519,7 +7519,7 @@
62E984BAC1C4D1DFC5C1EBBD /* BaseCourseListFlowLayout.swift in Sources */,
62E981A87A7859058F31C9F8 /* HorizontalCourseListFlowLayout.swift in Sources */,
62E98EF8366F3A12CA5092BF /* VerticalCourseListFlowLayout.swift in Sources */,
62E98D14FE058C5D8D39D63F /* CourseWidgetButton.swift in Sources */,
62E98D14FE058C5D8D39D63F /* CourseWidgetContinueLearningButton.swift in Sources */,
62E98833651A7E6B4D42D01B /* CourseWidgetCoverView.swift in Sources */,
62E989945806203AB464B5EF /* CourseWidgetLabel.swift in Sources */,
62E98E6F295E2C4799957DFB /* CourseWidgetStatsItemView.swift in Sources */,
Expand Down
7 changes: 0 additions & 7 deletions Stepic/Sources/Modules/CourseList/CourseListDataFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,6 @@ enum CourseList {
}
}

/// Click on secondary button
enum SecondaryCourseAction {
struct Request {
let viewModelUniqueIdentifier: UniqueIdentifierType
}
}

/// Click on course
enum MainCourseAction {
struct Request {
Expand Down
23 changes: 1 addition & 22 deletions Stepic/Sources/Modules/CourseList/CourseListInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ protocol CourseListInteractorProtocol: AnyObject {
func doCoursesFetch(request: CourseList.CoursesLoad.Request)
func doNextCoursesFetch(request: CourseList.NextCoursesLoad.Request)
func doPrimaryAction(request: CourseList.PrimaryCourseAction.Request)
func doSecondaryAction(request: CourseList.SecondaryCourseAction.Request)
func doMainAction(request: CourseList.MainCourseAction.Request)
}

Expand Down Expand Up @@ -227,7 +226,7 @@ final class CourseListInteractor: CourseListInteractorProtocol {
}
}

func doSecondaryAction(request: CourseList.SecondaryCourseAction.Request) {
func doMainAction(request: CourseList.MainCourseAction.Request) {
guard let targetIndex = self.currentCourses.firstIndex(where: { $0.0 == request.viewModelUniqueIdentifier }),
let targetCourse = self.currentCourses[safe: targetIndex]?.1 else {
fatalError("Invalid module state")
Expand All @@ -251,26 +250,6 @@ final class CourseListInteractor: CourseListInteractorProtocol {
}
}

func doMainAction(request: CourseList.MainCourseAction.Request) {
guard let targetIndex = self.currentCourses.firstIndex(where: { $0.0 == request.viewModelUniqueIdentifier }),
let targetCourse = self.currentCourses[safe: targetIndex]?.1 else {
fatalError("Invalid module state")
}

if targetCourse.enrolled && self.userAccountService.isAuthorized {
// Enrolled course -> open last step
self.moduleOutput?.presentLastStep(
course: targetCourse,
isAdaptive: self.adaptiveStorageManager.canOpenInAdaptiveMode(
courseId: targetCourse.id
)
)
} else {
// Unenrolled course -> info
self.moduleOutput?.presentCourseInfo(course: targetCourse)
}
}

// MARK: - Private methods

private func getAvailableAdaptiveCourses(from courses: [Course]) -> Set<Course> {
Expand Down
55 changes: 9 additions & 46 deletions Stepic/Sources/Modules/CourseList/CourseListPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ final class CourseListPresenter: CourseListPresenterProtocol {
isAdaptive: Bool,
isAuthorized: Bool
) -> CourseWidgetViewModel {
let summaryText: String = {
let summary = course.summary.trimmingCharacters(in: .whitespacesAndNewlines)
return summary.isEmpty
? course.courseDescription.trimmingCharacters(in: .whitespacesAndNewlines)
: summary
}()

var progressViewModel: CourseWidgetProgressViewModel?
if let progress = course.progress {
progressViewModel = self.makeProgressViewModel(progress: progress)
Expand All @@ -98,60 +105,16 @@ final class CourseListPresenter: CourseListPresenterProtocol {
ratingLabelText = FormatterHelper.averageRating(averageRating)
}

let primaryButtonText = self.makePrimaryButtonDescription(course: course)
let secondaryButtonText = self.makeSecondaryButtonDescription(
isEnrolled: course.enrolled,
isAdaptive: isAdaptive
)

return CourseWidgetViewModel(
title: course.title,
summary: summaryText,
coverImageURL: URL(string: course.coverURLString),
primaryButtonDescription: primaryButtonText,
secondaryButtonDescription: secondaryButtonText,
learnersLabelText: FormatterHelper.longNumber(course.learnersCount ?? 0),
ratingLabelText: ratingLabelText,
isAdaptive: isAdaptive,
isEnrolled: isAuthorized && course.enrolled,
progress: progressViewModel,
uniqueIdentifier: uniqueIdentifier
)
}

func makePrimaryButtonDescription(course: Course) -> CourseWidgetViewModel.ButtonDescription {
let isEnrolled = course.enrolled
let title: String = {
if isEnrolled {
return NSLocalizedString("WidgetButtonLearn", comment: "")
}

if course.isPaid, let displayPrice = course.displayPrice {
return String(format: NSLocalizedString("WidgetButtonBuy", comment: ""), displayPrice)
}

return NSLocalizedString("WidgetButtonJoin", comment: "")
}()

return CourseWidgetViewModel.ButtonDescription(
title: title,
isCallToAction: !isEnrolled
)
}

private func makeSecondaryButtonDescription(
isEnrolled: Bool,
isAdaptive: Bool
) -> CourseWidgetViewModel.ButtonDescription {
var title: String
if isAdaptive {
title = NSLocalizedString("WidgetButtonInfo", comment: "")
} else {
title = isEnrolled
? NSLocalizedString("WidgetButtonSyllabus", comment: "")
: NSLocalizedString("WidgetButtonInfo", comment: "")
}
return CourseWidgetViewModel.ButtonDescription(
title: title,
isCallToAction: false
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ protocol CourseListViewControllerProtocol: AnyObject {
protocol CourseListViewControllerDelegate: AnyObject {
func itemDidSelected(viewModel: CourseWidgetViewModel)
func primaryButtonClicked(viewModel: CourseWidgetViewModel)
func secondaryButtonClicked(viewModel: CourseWidgetViewModel)
}

class CourseListViewController: UIViewController {
Expand Down Expand Up @@ -133,12 +132,6 @@ extension CourseListViewController: CourseListViewControllerDelegate {
request: .init(viewModelUniqueIdentifier: viewModel.uniqueIdentifier)
)
}

func secondaryButtonClicked(viewModel: CourseWidgetViewModel) {
self.interactor.doSecondaryAction(
request: .init(viewModelUniqueIdentifier: viewModel.uniqueIdentifier)
)
}
}

final class HorizontalCourseListViewController: CourseListViewController {
Expand Down
6 changes: 2 additions & 4 deletions Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ struct CourseWidgetProgressViewModel {
}

struct CourseWidgetViewModel: UniqueIdentifiable {
typealias ButtonDescription = (title: String, isCallToAction: Bool)

let title: String
let summary: String
let coverImageURL: URL?
let primaryButtonDescription: ButtonDescription
let secondaryButtonDescription: ButtonDescription
let learnersLabelText: String
let ratingLabelText: String?
let isAdaptive: Bool
let isEnrolled: Bool
let progress: CourseWidgetProgressViewModel?
let uniqueIdentifier: UniqueIdentifierType
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import UIKit

protocol CourseListCollectionViewCellDelegate: AnyObject {
func widgetPrimaryButtonClicked(viewModel: CourseWidgetViewModel?)
func widgetSecondaryButtonClicked(viewModel: CourseWidgetViewModel?)
}

extension CourseListCollectionViewCell {
enum Appearance {
static let borderWidth: CGFloat = 1.0
static let cornerRadius: CGFloat = 13.0
}
}

class CourseListCollectionViewCell: UICollectionViewCell, Reusable {
Expand All @@ -14,22 +20,14 @@ class CourseListCollectionViewCell: UICollectionViewCell, Reusable {
private lazy var widgetView: CourseWidgetView = {
let widget = CourseWidgetView(colorMode: self.colorMode)
// Pass clicks from widget view to collection view delegate
widget.onPrimaryButtonClick = { [weak self] in
widget.onContinueLearningButtonClick = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.delegate?.widgetPrimaryButtonClicked(
viewModel: strongSelf.configurationViewModel
)
}
widget.onSecondaryButtonClick = { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.delegate?.widgetSecondaryButtonClicked(
viewModel: strongSelf.configurationViewModel
)
}
return widget
}()

Expand All @@ -51,6 +49,15 @@ class CourseListCollectionViewCell: UICollectionViewCell, Reusable {
fatalError("init(coder:) has not been implemented")
}

override func layoutSubviews() {
super.layoutSubviews()

self.layer.cornerRadius = Appearance.cornerRadius
self.layer.borderWidth = Appearance.borderWidth
self.layer.borderColor = self.colorMode.courseWidgetBorderColor.cgColor
self.layer.masksToBounds = true
}

func configure(viewModel: CourseWidgetViewModel) {
self.widgetView.configure(viewModel: viewModel)
self.configurationViewModel = viewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,4 @@ extension CourseListCollectionViewDataSource: CourseListCollectionViewCellDelega

self.delegate?.primaryButtonClicked(viewModel: viewModel)
}

func widgetSecondaryButtonClicked(viewModel: CourseWidgetViewModel?) {
guard let viewModel = viewModel else {
return
}

self.delegate?.secondaryButtonClicked(viewModel: viewModel)
}
}
47 changes: 30 additions & 17 deletions Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,8 @@ extension CourseListColorMode {
}
}

var courseWidgetButtonAppearance: CourseWidgetButton.Appearance {
switch self {
case .light:
return .init(
textColor: .stepikPrimaryText,
backgroundColor: .stepikLightSecondaryBackground,
callToActionTextColor: .stepikCallToActionText,
callToActionBackgroundColor: .stepikCallToActionBackground
)
case .dark:
return .init(
textColor: .white,
backgroundColor: UIColor.white.withAlphaComponent(0.1),
callToActionTextColor: .stepikCallToActionText,
callToActionBackgroundColor: .stepikCallToActionBackground
)
}
var courseWidgetContinueLearningButtonAppearance: CourseWidgetContinueLearningButton.Appearance {
.init(iconTintColor: .stepikGreen, textColor: .stepikGreen)
}

var courseWidgetStatsViewAppearance: CourseWidgetStatsView.Appearance {
Expand Down Expand Up @@ -109,4 +94,32 @@ extension CourseListColorMode {
return appearance
}
}

var courseWidgetSummaryLabelAppearance: CourseWidgetLabel.Appearance {
var appearance = CourseWidgetLabel.Appearance(
maxLinesCount: 0,
font: .systemFont(ofSize: 12, weight: .regular)
)

switch self {
case .light:
appearance.textColor = .stepikSecondaryText
case .dark:
appearance.textColor = UIColor.dynamic(
light: UIColor.white.withAlphaComponent(0.6),
dark: .stepikSecondaryText
)
}

return appearance
}

var courseWidgetBorderColor: UIColor {
switch self {
case .light:
return .dynamic(light: .stepikGrey8Fixed, dark: .stepikSeparator)
case .dark:
return .stepikSeparator
}
}
}
9 changes: 3 additions & 6 deletions Stepic/Sources/Modules/CourseList/Views/CourseListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ extension CourseListView {
struct Appearance {
let layoutMinimumLineSpacing: CGFloat = 16.0
let layoutMinimumInteritemSpacing: CGFloat = 16.0
let layoutItemHeight: CGFloat = 140.0
let layoutItemHeight: CGFloat = 160.0

let lightModeBackgroundColor = UIColor.stepikBackground
let darkModeBackgroundColor = UIColor.stepikAccentFixed
let darkModeDarkInterfaceBackgroundColor = UIColor.stepikSecondaryBackground
let darkModeBackgroundColor = UIColor.dynamic(light: .stepikAccent, dark: .stepikSecondaryBackground)

let horizontalLayoutNextPageWidth: CGFloat = 12.0
}
Expand Down Expand Up @@ -104,9 +103,7 @@ class CourseListView: UIView {
case .light:
return self.appearance.lightModeBackgroundColor
case .dark:
return self.isDarkInterfaceStyle
? self.appearance.darkModeDarkInterfaceBackgroundColor
: self.appearance.darkModeBackgroundColor
return self.appearance.darkModeBackgroundColor
}
}

Expand Down
Loading