From bb119f191c901ef6012863658082df9c95c35aa8 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 13 Apr 2020 12:55:36 +0300 Subject: [PATCH 1/3] Update course widget appearance --- Stepic.xcodeproj/project.pbxproj | 8 +- .../CourseList/CourseListDataFlow.swift | 7 - .../CourseList/CourseListInteractor.swift | 23 +-- .../CourseList/CourseListPresenter.swift | 55 +----- .../CourseList/CourseListViewController.swift | 7 - .../CourseList/CourseWidgetViewModel.swift | 6 +- .../Views/CourseListCollectionViewCell.swift | 27 ++- .../CourseListCollectionViewDataSource.swift | 8 - .../Views/CourseListColorMode.swift | 47 +++-- .../CourseList/Views/CourseListView.swift | 9 +- .../Views/Widget/CourseWidgetButton.swift | 59 ------ .../CourseWidgetContinueLearningButton.swift | 79 ++++++++ .../Views/Widget/CourseWidgetView.swift | 173 ++++++++++-------- Stepic/Theme/ColorPalette.swift | 3 + Stepic/en.lproj/Localizable.strings | 4 +- Stepic/ru.lproj/Localizable.strings | 2 +- 16 files changed, 249 insertions(+), 268 deletions(-) delete mode 100644 Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetButton.swift create mode 100644 Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetContinueLearningButton.swift diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index ffabb5a10a..9ca69cb826 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -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 */; }; @@ -2186,7 +2186,7 @@ 62E98A81E414B0314D76BC70 /* WriteCommentSolutionControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WriteCommentSolutionControl.swift; sourceTree = ""; }; 62E98A9C4517D3C76CA66D49 /* CourseInfoTabInfoHeaderBlockView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseInfoTabInfoHeaderBlockView.swift; sourceTree = ""; }; 62E98AA14C7255A86152B53B /* ExploreBlockHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExploreBlockHeaderView.swift; sourceTree = ""; }; - 62E98AA2D2D17D34EBAEC4AA /* CourseWidgetButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseWidgetButton.swift; sourceTree = ""; }; + 62E98AA2D2D17D34EBAEC4AA /* CourseWidgetContinueLearningButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseWidgetContinueLearningButton.swift; sourceTree = ""; }; 62E98AA5B57A1D9799E79B0B /* NewMatchingQuizViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewMatchingQuizViewModel.swift; sourceTree = ""; }; 62E98ABA6ADA2DEA8E21746B /* CourseInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseInfoView.swift; sourceTree = ""; }; 62E98AC188792B6E7B8EABC9 /* CourseListCollectionOutputProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseListCollectionOutputProtocol.swift; sourceTree = ""; }; @@ -5816,7 +5816,7 @@ 62E98B9687B645AE04CB6B5D /* Widget */ = { isa = PBXGroup; children = ( - 62E98AA2D2D17D34EBAEC4AA /* CourseWidgetButton.swift */, + 62E98AA2D2D17D34EBAEC4AA /* CourseWidgetContinueLearningButton.swift */, 62E98A35E88BBE09B6D20E70 /* CourseWidgetCoverView.swift */, 62E986B68558033E8B3DDF6A /* CourseWidgetLabel.swift */, 62E98E718321FFBD61C5A671 /* CourseWidgetStatsItemView.swift */, @@ -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 */, diff --git a/Stepic/Sources/Modules/CourseList/CourseListDataFlow.swift b/Stepic/Sources/Modules/CourseList/CourseListDataFlow.swift index 8394579ae2..0c85f7aa35 100644 --- a/Stepic/Sources/Modules/CourseList/CourseListDataFlow.swift +++ b/Stepic/Sources/Modules/CourseList/CourseListDataFlow.swift @@ -58,13 +58,6 @@ enum CourseList { } } - /// Click on secondary button - enum SecondaryCourseAction { - struct Request { - let viewModelUniqueIdentifier: UniqueIdentifierType - } - } - /// Click on course enum MainCourseAction { struct Request { diff --git a/Stepic/Sources/Modules/CourseList/CourseListInteractor.swift b/Stepic/Sources/Modules/CourseList/CourseListInteractor.swift index f0f18d5ca4..3e5c70b2c2 100644 --- a/Stepic/Sources/Modules/CourseList/CourseListInteractor.swift +++ b/Stepic/Sources/Modules/CourseList/CourseListInteractor.swift @@ -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) } @@ -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") @@ -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 { diff --git a/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift b/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift index c5a3d81939..2617af9354 100644 --- a/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift +++ b/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift @@ -86,6 +86,13 @@ final class CourseListPresenter: CourseListPresenterProtocol { isAdaptive: Bool, isAuthorized: Bool ) -> CourseWidgetViewModel { + let summaryText: String = { + let text = course.summary.isEmpty + ? course.courseDescription + : course.summary + return text.trimmingCharacters(in: .whitespacesAndNewlines) + }() + var progressViewModel: CourseWidgetProgressViewModel? if let progress = course.progress { progressViewModel = self.makeProgressViewModel(progress: progress) @@ -98,60 +105,18 @@ final class CourseListPresenter: CourseListPresenterProtocol { ratingLabelText = FormatterHelper.averageRating(averageRating) } - let primaryButtonText = self.makePrimaryButtonDescription(course: course) - let secondaryButtonText = self.makeSecondaryButtonDescription( - isEnrolled: course.enrolled, - isAdaptive: isAdaptive - ) + let isContinueLearningAvailable = isAuthorized && course.enrolled 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, + isContinueLearningAvailable: isContinueLearningAvailable, 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 - ) - } } diff --git a/Stepic/Sources/Modules/CourseList/CourseListViewController.swift b/Stepic/Sources/Modules/CourseList/CourseListViewController.swift index a99986e967..afe772054d 100644 --- a/Stepic/Sources/Modules/CourseList/CourseListViewController.swift +++ b/Stepic/Sources/Modules/CourseList/CourseListViewController.swift @@ -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 { @@ -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 { diff --git a/Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift b/Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift index bac51b4cb5..8eec7addab 100644 --- a/Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift +++ b/Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift @@ -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 isContinueLearningAvailable: Bool let progress: CourseWidgetProgressViewModel? let uniqueIdentifier: UniqueIdentifierType } diff --git a/Stepic/Sources/Modules/CourseList/Views/CourseListCollectionViewCell.swift b/Stepic/Sources/Modules/CourseList/Views/CourseListCollectionViewCell.swift index 100710d494..ac4e930e75 100644 --- a/Stepic/Sources/Modules/CourseList/Views/CourseListCollectionViewCell.swift +++ b/Stepic/Sources/Modules/CourseList/Views/CourseListCollectionViewCell.swift @@ -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 { @@ -14,7 +20,7 @@ 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 } @@ -22,14 +28,6 @@ class CourseListCollectionViewCell: UICollectionViewCell, Reusable { viewModel: strongSelf.configurationViewModel ) } - widget.onSecondaryButtonClick = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.delegate?.widgetSecondaryButtonClicked( - viewModel: strongSelf.configurationViewModel - ) - } return widget }() @@ -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 diff --git a/Stepic/Sources/Modules/CourseList/Views/CourseListCollectionViewDataSource.swift b/Stepic/Sources/Modules/CourseList/Views/CourseListCollectionViewDataSource.swift index 0f143b6e10..9be811777d 100644 --- a/Stepic/Sources/Modules/CourseList/Views/CourseListCollectionViewDataSource.swift +++ b/Stepic/Sources/Modules/CourseList/Views/CourseListCollectionViewDataSource.swift @@ -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) - } } diff --git a/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift b/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift index 631cc62634..2f84101949 100644 --- a/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift +++ b/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift @@ -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 courseWidgetLearnButtonAppearance: CourseWidgetContinueLearningButton.Appearance { + .init(iconTintColor: .stepikGreen, textColor: .stepikGreen) } var courseWidgetStatsViewAppearance: CourseWidgetStatsView.Appearance { @@ -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 + } + } } diff --git a/Stepic/Sources/Modules/CourseList/Views/CourseListView.swift b/Stepic/Sources/Modules/CourseList/Views/CourseListView.swift index 185ae01478..50036072e5 100644 --- a/Stepic/Sources/Modules/CourseList/Views/CourseListView.swift +++ b/Stepic/Sources/Modules/CourseList/Views/CourseListView.swift @@ -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 } @@ -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 } } diff --git a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetButton.swift b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetButton.swift deleted file mode 100644 index f81406dad8..0000000000 --- a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetButton.swift +++ /dev/null @@ -1,59 +0,0 @@ -import UIKit - -extension CourseWidgetButton { - struct Appearance { - let cornerRadius: CGFloat = 8.0 - - let titleFont = UIFont.systemFont(ofSize: 14, weight: .regular) - - var textColor = UIColor.stepikPrimaryText - var backgroundColor = UIColor.stepikLightSecondaryBackground - - var callToActionTextColor = UIColor.stepikCallToActionText - var callToActionBackgroundColor = UIColor.stepikCallToActionBackground - } -} - -final class CourseWidgetButton: BounceButton { - let appearance: Appearance - - var isCallToAction: Bool { - didSet { - self.updateColors() - } - } - - init( - isCallToAction: Bool = false, - appearance: Appearance = Appearance() - ) { - self.appearance = appearance - self.isCallToAction = isCallToAction - super.init(frame: .zero) - self.setupView() - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func updateColors() { - self.backgroundColor = self.isCallToAction - ? self.appearance.callToActionBackgroundColor - : self.appearance.backgroundColor - self.setTitleColor( - self.isCallToAction ? self.appearance.callToActionTextColor : self.appearance.textColor, - for: .normal - ) - } -} - -extension CourseWidgetButton: ProgrammaticallyInitializableViewProtocol { - func setupView() { - self.titleLabel?.font = self.appearance.titleFont - - self.clipsToBounds = true - self.layer.cornerRadius = self.appearance.cornerRadius - } -} diff --git a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetContinueLearningButton.swift b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetContinueLearningButton.swift new file mode 100644 index 0000000000..2fdd77365d --- /dev/null +++ b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetContinueLearningButton.swift @@ -0,0 +1,79 @@ +import SnapKit +import UIKit + +extension CourseWidgetContinueLearningButton { + struct Appearance { + let iconSize = CGSize(width: 11, height: 13) + let iconInsets = LayoutInsets(left: 16) + var iconTintColor = UIColor.stepikGreen + + let textFont = UIFont.systemFont(ofSize: 16, weight: .regular) + var textColor = UIColor.stepikGreen + } +} + +final class CourseWidgetContinueLearningButton: UIControl { + let appearance: Appearance + + private lazy var iconImageView: UIImageView = { + let image = UIImage(named: "step-next-navigation-icon") + let imageView = UIImageView(image: image?.withRenderingMode(.alwaysTemplate)) + imageView.tintColor = self.appearance.iconTintColor + return imageView + }() + + private lazy var textLabel: UILabel = { + let label = UILabel() + label.font = self.appearance.textFont + label.textColor = self.appearance.textColor + label.textAlignment = .center + label.text = NSLocalizedString("WidgetButtonLearn", comment: "") + return label + }() + + override var isHighlighted: Bool { + didSet { + self.iconImageView.alpha = self.isHighlighted ? 0.5 : 1.0 + self.textLabel.alpha = self.isHighlighted ? 0.5 : 1.0 + } + } + + init(appearance: Appearance = Appearance()) { + self.appearance = appearance + super.init(frame: .zero) + + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension CourseWidgetContinueLearningButton: ProgrammaticallyInitializableViewProtocol { + func addSubviews() { + self.addSubview(self.textLabel) + self.addSubview(self.iconImageView) + } + + func makeConstraints() { + self.textLabel.translatesAutoresizingMaskIntoConstraints = false + self.textLabel.snp.makeConstraints { make in + make.leading + .trailing + .centerY + .equalToSuperview() + } + + self.iconImageView.translatesAutoresizingMaskIntoConstraints = false + self.iconImageView.snp.makeConstraints { make in + make.leading + .equalToSuperview() + .offset(self.appearance.iconInsets.left) + make.centerY.equalToSuperview() + make.size.equalTo(self.appearance.iconSize) + } + } +} diff --git a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetView.swift b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetView.swift index ecadd5716c..da7a7bd512 100644 --- a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetView.swift +++ b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetView.swift @@ -3,15 +3,19 @@ import UIKit extension CourseWidgetView { struct Appearance { + let coverViewInsets = LayoutInsets(top: 16, left: 16) let coverViewWidthHeight: CGFloat = 80.0 - let secondaryActionButtonInsets = UIEdgeInsets(top: 12, left: 0, bottom: 0, right: 10) - let secondaryActionButtonSize = CGSize(width: 80, height: 48) - - let mainActionButtonHeight: CGFloat = 48.0 + let titleLabelInsets = LayoutInsets(left: 8, right: 16) let statsViewHeight: CGFloat = 17 - let statsViewInsets = UIEdgeInsets(top: 8, left: 9, bottom: 12, right: 0) + let statsViewInsets = LayoutInsets(top: 8) + + let summaryLabelInsets = LayoutInsets(top: 8, left: 16, bottom: 16, right: 16) + + let continueLearningButtonInsets = LayoutInsets(top: 16) + + let separatorHeight: CGFloat = 0.5 } } @@ -21,22 +25,6 @@ final class CourseWidgetView: UIView { private lazy var coverView = CourseWidgetCoverView() - private lazy var primaryActionButton: CourseWidgetButton = { - let button = CourseWidgetButton( - appearance: self.colorMode.courseWidgetButtonAppearance - ) - button.addTarget(self, action: #selector(self.primaryActionButtonClicked), for: .touchUpInside) - return button - }() - - private lazy var secondaryActionButton: CourseWidgetButton = { - let button = CourseWidgetButton( - appearance: self.colorMode.courseWidgetButtonAppearance - ) - button.addTarget(self, action: #selector(self.secondaryActionButtonClicked), for: .touchUpInside) - return button - }() - private lazy var titleLabel = CourseWidgetLabel( appearance: self.colorMode.courseWidgetLabelAppearance ) @@ -44,8 +32,23 @@ final class CourseWidgetView: UIView { appearance: self.colorMode.courseWidgetStatsViewAppearance ) - var onPrimaryButtonClick: (() -> Void)? - var onSecondaryButtonClick: (() -> Void)? + private lazy var separatorView: UIView = { + let view = UIView() + view.backgroundColor = self.colorMode.courseWidgetBorderColor + return view + }() + + private lazy var summaryLabel = CourseWidgetLabel( + appearance: self.colorMode.courseWidgetSummaryLabelAppearance + ) + + private lazy var continueLearningButton: CourseWidgetContinueLearningButton = { + let button = CourseWidgetContinueLearningButton(appearance: self.colorMode.courseWidgetLearnButtonAppearance) + button.addTarget(self, action: #selector(self.continueLearningButtonClicked), for: .touchUpInside) + return button + }() + + var onContinueLearningButtonClick: (() -> Void)? init( frame: CGRect = .zero, @@ -70,21 +73,10 @@ final class CourseWidgetView: UIView { self.coverView.coverImageURL = viewModel.coverImageURL self.coverView.shouldShowAdaptiveMark = viewModel.isAdaptive - self.primaryActionButton.setTitle( - viewModel.primaryButtonDescription.title, - for: .normal - ) - self.primaryActionButton.isCallToAction = viewModel - .primaryButtonDescription - .isCallToAction - - self.secondaryActionButton.setTitle( - viewModel.secondaryButtonDescription.title, - for: .normal - ) - self.secondaryActionButton.isCallToAction = viewModel - .secondaryButtonDescription - .isCallToAction + self.summaryLabel.text = viewModel.summary + self.summaryLabel.isHidden = viewModel.isContinueLearningAvailable + self.separatorView.isHidden = !viewModel.isContinueLearningAvailable + self.continueLearningButton.isHidden = !viewModel.isContinueLearningAvailable self.statsView.learnersLabelText = viewModel.learnersLabelText self.statsView.ratingLabelText = viewModel.ratingLabelText @@ -95,73 +87,102 @@ final class CourseWidgetView: UIView { self.statsView.progress = viewModel } - // MARK: Button selectors - - @objc - private func primaryActionButtonClicked() { - self.onPrimaryButtonClick?() - } + // MARK: Private API @objc - private func secondaryActionButtonClicked() { - self.onSecondaryButtonClick?() + private func continueLearningButtonClicked() { + self.onContinueLearningButtonClick?() } } extension CourseWidgetView: ProgrammaticallyInitializableViewProtocol { func addSubviews() { self.addSubview(self.coverView) - self.addSubview(self.primaryActionButton) - self.addSubview(self.secondaryActionButton) self.addSubview(self.titleLabel) self.addSubview(self.statsView) + self.addSubview(self.summaryLabel) + self.addSubview(self.continueLearningButton) + self.addSubview(self.separatorView) } func makeConstraints() { self.coverView.translatesAutoresizingMaskIntoConstraints = false self.coverView.snp.makeConstraints { make in - make.leading.top.equalToSuperview() - make.height.width.equalTo(self.appearance.coverViewWidthHeight) - } - - self.primaryActionButton.translatesAutoresizingMaskIntoConstraints = false - self.primaryActionButton.snp.makeConstraints { make in - make.bottom.trailing.equalToSuperview() - make.height.equalTo(self.appearance.mainActionButtonHeight) + make.top + .equalToSuperview() + .offset(self.appearance.coverViewInsets.top) + make.leading + .equalToSuperview() + .offset(self.appearance.coverViewInsets.left) + make.height + .width + .equalTo(self.appearance.coverViewWidthHeight) } - self.secondaryActionButton.translatesAutoresizingMaskIntoConstraints = false - self.secondaryActionButton.snp.makeConstraints { make in - make.size.equalTo(self.appearance.secondaryActionButtonSize) - make.leading.bottom.equalToSuperview() + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.titleLabel.snp.makeConstraints { make in make.top - .equalTo(self.coverView.snp.bottom) - .offset(self.appearance.secondaryActionButtonInsets.top) + .equalTo(self.coverView.snp.top) + make.leading + .equalTo(self.coverView.snp.trailing) + .offset(self.appearance.titleLabelInsets.left) make.trailing - .equalTo(self.primaryActionButton.snp.leading) - .offset(-self.appearance.secondaryActionButtonInsets.right) + .equalToSuperview() + .offset(-self.appearance.titleLabelInsets.right) } self.statsView.translatesAutoresizingMaskIntoConstraints = false self.statsView.snp.makeConstraints { make in - make.trailing.equalToSuperview() - make.bottom - .lessThanOrEqualTo(self.primaryActionButton.snp.top) - .offset(-self.appearance.statsViewInsets.bottom) - make.leading - .equalTo(self.coverView.snp.trailing) - .offset(self.appearance.statsViewInsets.left) - make.height.equalTo(self.appearance.statsViewHeight) make.top .equalTo(self.titleLabel.snp.bottom) .offset(self.appearance.statsViewInsets.top) .priority(.low) + make.leading + .equalTo(self.titleLabel.snp.leading) + make.bottom + .lessThanOrEqualTo(self.coverView.snp.bottom) + make.trailing + .equalTo(self.titleLabel.snp.trailing) + make.height + .equalTo(self.appearance.statsViewHeight) } - self.titleLabel.translatesAutoresizingMaskIntoConstraints = false - self.titleLabel.snp.makeConstraints { make in - make.top.trailing.equalToSuperview() - make.leading.equalTo(self.statsView.snp.leading) + self.summaryLabel.translatesAutoresizingMaskIntoConstraints = false + self.summaryLabel.snp.makeConstraints { make in + make.top + .equalTo(self.coverView.snp.bottom) + .offset(self.appearance.summaryLabelInsets.top) + make.leading + .equalToSuperview() + .offset(self.appearance.summaryLabelInsets.left) + make.bottom + .equalToSuperview() + .offset(-self.appearance.summaryLabelInsets.bottom) + make.trailing + .equalToSuperview() + .offset(-self.appearance.summaryLabelInsets.right) + } + + self.continueLearningButton.translatesAutoresizingMaskIntoConstraints = false + self.continueLearningButton.snp.makeConstraints { make in + make.top + .equalTo(self.coverView.snp.bottom) + .offset(self.appearance.continueLearningButtonInsets.top) + make.leading + .bottom + .trailing + .equalToSuperview() + } + + self.separatorView.translatesAutoresizingMaskIntoConstraints = false + self.separatorView.snp.makeConstraints { make in + make.leading + .trailing + .equalToSuperview() + make.bottom + .equalTo(self.continueLearningButton.snp.top) + make.height + .equalTo(self.appearance.separatorHeight) } } } diff --git a/Stepic/Theme/ColorPalette.swift b/Stepic/Theme/ColorPalette.swift index d770f11707..c1e984ff16 100644 --- a/Stepic/Theme/ColorPalette.swift +++ b/Stepic/Theme/ColorPalette.swift @@ -138,6 +138,8 @@ extension UIColor { /// A non adaptable color with hex value #F6F6F6 (grey07). static let stepikGreyFixed = ColorPalette.grey100 + /// A non adaptable color with hex value #EAEAEA (grey08). + static let stepikGrey8Fixed = ColorPalette.grey08 // MARK: Violet @@ -702,6 +704,7 @@ private enum ColorPalette { static let grey050 = UIColor(hex6: 0xFAFAFA) static let grey04 = UIColor(hex6: 0xEAECF0) + static let grey08 = UIColor(hex6: 0xEAEAEA) // MARK: - Violet - diff --git a/Stepic/en.lproj/Localizable.strings b/Stepic/en.lproj/Localizable.strings index 196279fc25..9f791b5e4c 100644 --- a/Stepic/en.lproj/Localizable.strings +++ b/Stepic/en.lproj/Localizable.strings @@ -318,7 +318,7 @@ AnonymousNotificationsTitle = "Anonymous users can't receive notifications"; SignInToHaveNotifications = "Sign in to be able to receive notifications"; BadConnectionAuth = "Check your internet connection"; -ContinueLearningWidgetButtonTitle = "Continue learning"; +ContinueLearningWidgetButtonTitle = "Continue Learning"; ShowAll = "All"; ShowAllTestDetailIndicator = "All ›"; ShowAllTestText = "See All"; @@ -346,7 +346,7 @@ TrendingTopics = "Trending topics"; WidgetButtonJoin = "Join"; WidgetButtonInfo = "Info"; WidgetButtonSyllabus = "Syllabus"; -WidgetButtonLearn = "Learn"; +WidgetButtonLearn = "Continue Learning"; WidgetButtonBuy = "Buy with %@"; WidgetAdaptiveLabel = "ADAPTIVE"; diff --git a/Stepic/ru.lproj/Localizable.strings b/Stepic/ru.lproj/Localizable.strings index 4242866b1a..976efb51f3 100644 --- a/Stepic/ru.lproj/Localizable.strings +++ b/Stepic/ru.lproj/Localizable.strings @@ -347,7 +347,7 @@ TrendingTopics = "Категории"; WidgetButtonJoin = "Поступить"; WidgetButtonInfo = "Инфо"; WidgetButtonSyllabus = "План"; -WidgetButtonLearn = "Учиться"; +WidgetButtonLearn = "Продолжить учиться"; WidgetAdaptiveLabel = "АДАПТИВНЫЙ"; WidgetButtonBuy = "Купить за %@"; From a202d60cdabe5a258629ffa93b9a7a196c67e768 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 13 Apr 2020 13:04:28 +0300 Subject: [PATCH 2/3] Update summary text --- .../Sources/Modules/CourseList/CourseListPresenter.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift b/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift index 2617af9354..6eafe43f10 100644 --- a/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift +++ b/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift @@ -87,10 +87,10 @@ final class CourseListPresenter: CourseListPresenterProtocol { isAuthorized: Bool ) -> CourseWidgetViewModel { let summaryText: String = { - let text = course.summary.isEmpty - ? course.courseDescription - : course.summary - return text.trimmingCharacters(in: .whitespacesAndNewlines) + let summary = course.summary.trimmingCharacters(in: .whitespacesAndNewlines) + return summary.isEmpty + ? course.courseDescription.trimmingCharacters(in: .whitespacesAndNewlines) + : summary }() var progressViewModel: CourseWidgetProgressViewModel? From 280056961145f49c21c3e7964becb51d8565ae53 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 13 Apr 2020 13:08:47 +0300 Subject: [PATCH 3/3] Clean up --- .../Modules/CourseList/CourseListPresenter.swift | 4 +--- .../Modules/CourseList/CourseWidgetViewModel.swift | 2 +- .../Modules/CourseList/Views/CourseListColorMode.swift | 2 +- .../CourseList/Views/Widget/CourseWidgetView.swift | 10 ++++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift b/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift index 6eafe43f10..cdbd24cea7 100644 --- a/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift +++ b/Stepic/Sources/Modules/CourseList/CourseListPresenter.swift @@ -105,8 +105,6 @@ final class CourseListPresenter: CourseListPresenterProtocol { ratingLabelText = FormatterHelper.averageRating(averageRating) } - let isContinueLearningAvailable = isAuthorized && course.enrolled - return CourseWidgetViewModel( title: course.title, summary: summaryText, @@ -114,7 +112,7 @@ final class CourseListPresenter: CourseListPresenterProtocol { learnersLabelText: FormatterHelper.longNumber(course.learnersCount ?? 0), ratingLabelText: ratingLabelText, isAdaptive: isAdaptive, - isContinueLearningAvailable: isContinueLearningAvailable, + isEnrolled: isAuthorized && course.enrolled, progress: progressViewModel, uniqueIdentifier: uniqueIdentifier ) diff --git a/Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift b/Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift index 8eec7addab..6d43a9040a 100644 --- a/Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift +++ b/Stepic/Sources/Modules/CourseList/CourseWidgetViewModel.swift @@ -12,7 +12,7 @@ struct CourseWidgetViewModel: UniqueIdentifiable { let learnersLabelText: String let ratingLabelText: String? let isAdaptive: Bool - let isContinueLearningAvailable: Bool + let isEnrolled: Bool let progress: CourseWidgetProgressViewModel? let uniqueIdentifier: UniqueIdentifierType } diff --git a/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift b/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift index 2f84101949..898228afbe 100644 --- a/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift +++ b/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift @@ -59,7 +59,7 @@ extension CourseListColorMode { } } - var courseWidgetLearnButtonAppearance: CourseWidgetContinueLearningButton.Appearance { + var courseWidgetContinueLearningButtonAppearance: CourseWidgetContinueLearningButton.Appearance { .init(iconTintColor: .stepikGreen, textColor: .stepikGreen) } diff --git a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetView.swift b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetView.swift index da7a7bd512..19d1ac6b47 100644 --- a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetView.swift +++ b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetView.swift @@ -43,7 +43,9 @@ final class CourseWidgetView: UIView { ) private lazy var continueLearningButton: CourseWidgetContinueLearningButton = { - let button = CourseWidgetContinueLearningButton(appearance: self.colorMode.courseWidgetLearnButtonAppearance) + let button = CourseWidgetContinueLearningButton( + appearance: self.colorMode.courseWidgetContinueLearningButtonAppearance + ) button.addTarget(self, action: #selector(self.continueLearningButtonClicked), for: .touchUpInside) return button }() @@ -74,9 +76,9 @@ final class CourseWidgetView: UIView { self.coverView.shouldShowAdaptiveMark = viewModel.isAdaptive self.summaryLabel.text = viewModel.summary - self.summaryLabel.isHidden = viewModel.isContinueLearningAvailable - self.separatorView.isHidden = !viewModel.isContinueLearningAvailable - self.continueLearningButton.isHidden = !viewModel.isContinueLearningAvailable + self.summaryLabel.isHidden = viewModel.isEnrolled + self.separatorView.isHidden = !viewModel.isEnrolled + self.continueLearningButton.isHidden = !viewModel.isEnrolled self.statsView.learnersLabelText = viewModel.learnersLabelText self.statsView.ratingLabelText = viewModel.ratingLabelText