From 49cafb851de1cf050509a5a104666291cab19dad Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 14 Jan 2021 00:01:38 +0300 Subject: [PATCH 1/9] Move stories from catalog to home (#872) * Remove stories submodule from explore * Add stories submodule to home * Clean up --- .../Modules/Explore/ExploreDataFlow.swift | 25 -------- .../Modules/Explore/ExploreInteractor.swift | 10 --- .../Modules/Explore/ExplorePresenter.swift | 12 ---- .../Explore/ExploreViewController.swift | 55 ----------------- .../Sources/Modules/Home/HomeDataFlow.swift | 23 +++++++ .../Sources/Modules/Home/HomeInteractor.swift | 10 +++ .../Sources/Modules/Home/HomePresenter.swift | 10 +++ .../Modules/Home/HomeViewController.swift | 61 ++++++++++++++++++- 8 files changed, 101 insertions(+), 105 deletions(-) diff --git a/Stepic/Sources/Modules/Explore/ExploreDataFlow.swift b/Stepic/Sources/Modules/Explore/ExploreDataFlow.swift index 393dfcddc0..20efe63288 100644 --- a/Stepic/Sources/Modules/Explore/ExploreDataFlow.swift +++ b/Stepic/Sources/Modules/Explore/ExploreDataFlow.swift @@ -4,7 +4,6 @@ enum Explore { // MARK: Submodules identifiers enum Submodule: String, UniqueIdentifiable { - case stories case languageSwitch case catalogBlocks case visitedCourses @@ -40,19 +39,6 @@ enum Explore { } } - /// Update stories visibility - enum StoriesVisibilityUpdate { - @available(*, deprecated, message: "Should be refactored with VIP cycle as CheckLanguageSwitchAvailability") - struct Response { - let isHidden: Bool - } - - @available(*, deprecated, message: "Should be refactored with VIP cycle as CheckLanguageSwitchAvailability") - struct ViewModel { - let isHidden: Bool - } - } - // Refresh course block enum CourseListStateUpdate { enum State { @@ -71,17 +57,6 @@ enum Explore { } } - /// Update status bar style (called by stories module) - enum StatusBarStyleUpdate { - struct Response { - let statusBarStyle: UIStatusBarStyle - } - - struct ViewModel { - let statusBarStyle: UIStatusBarStyle - } - } - /// Start search for courses enum SearchCourses { struct ViewModel {} diff --git a/Stepic/Sources/Modules/Explore/ExploreInteractor.swift b/Stepic/Sources/Modules/Explore/ExploreInteractor.swift index 346af3eed2..771f858f8e 100644 --- a/Stepic/Sources/Modules/Explore/ExploreInteractor.swift +++ b/Stepic/Sources/Modules/Explore/ExploreInteractor.swift @@ -98,16 +98,6 @@ final class ExploreInteractor: BaseExploreInteractor, ExploreInteractorProtocol } } -extension ExploreInteractor: StoriesOutputProtocol { - func hideStories() { - self.explorePresenter?.presentStoriesBlock(response: .init(isHidden: true)) - } - - func handleStoriesStatusBarStyleUpdate(_ statusBarStyle: UIStatusBarStyle) { - self.explorePresenter?.presentStatusBarStyle(response: .init(statusBarStyle: statusBarStyle)) - } -} - extension ExploreInteractor: CourseListFilterOutputProtocol { func handleCourseListFilterDidFinishWithFilters(_ filters: [CourseListFilter.Filter]) { self.doSearchResultsCourseListFiltersUpdate(request: .init(filters: filters)) diff --git a/Stepic/Sources/Modules/Explore/ExplorePresenter.swift b/Stepic/Sources/Modules/Explore/ExplorePresenter.swift index d060518e9b..3cd4f6b95c 100644 --- a/Stepic/Sources/Modules/Explore/ExplorePresenter.swift +++ b/Stepic/Sources/Modules/Explore/ExplorePresenter.swift @@ -3,9 +3,7 @@ import UIKit protocol ExplorePresenterProtocol: BaseExplorePresenterProtocol { func presentContent(response: Explore.ContentLoad.Response) func presentLanguageSwitchBlock(response: Explore.LanguageSwitchAvailabilityCheck.Response) - func presentStoriesBlock(response: Explore.StoriesVisibilityUpdate.Response) func presentCourseListState(response: Explore.CourseListStateUpdate.Response) - func presentStatusBarStyle(response: Explore.StatusBarStyleUpdate.Response) func presentCourseListFilter(response: Explore.CourseListFilterPresentation.Response) func presentSearchResultsCourseListFilters(response: Explore.SearchResultsCourseListFiltersUpdate.Response) } @@ -25,12 +23,6 @@ final class ExplorePresenter: BaseExplorePresenter, ExplorePresenterProtocol { ) } - func presentStoriesBlock(response: Explore.StoriesVisibilityUpdate.Response) { - self.exploreViewController?.displayStoriesBlock( - viewModel: .init(isHidden: response.isHidden) - ) - } - func presentCourseListState(response: Explore.CourseListStateUpdate.Response) { self.exploreViewController?.displayModuleErrorState( viewModel: .init( @@ -40,10 +32,6 @@ final class ExplorePresenter: BaseExplorePresenter, ExplorePresenterProtocol { ) } - func presentStatusBarStyle(response: Explore.StatusBarStyleUpdate.Response) { - self.exploreViewController?.displayStatusBarStyle(viewModel: .init(statusBarStyle: response.statusBarStyle)) - } - func presentCourseListFilter(response: Explore.CourseListFilterPresentation.Response) { let presentationDescription = CourseListFilter.PresentationDescription( availableFilters: .all, diff --git a/Stepic/Sources/Modules/Explore/ExploreViewController.swift b/Stepic/Sources/Modules/Explore/ExploreViewController.swift index 40daebf6da..8bde521bff 100644 --- a/Stepic/Sources/Modules/Explore/ExploreViewController.swift +++ b/Stepic/Sources/Modules/Explore/ExploreViewController.swift @@ -4,9 +4,7 @@ import UIKit protocol ExploreViewControllerProtocol: BaseExploreViewControllerProtocol { func displayContent(viewModel: Explore.ContentLoad.ViewModel) func displayLanguageSwitchBlock(viewModel: Explore.LanguageSwitchAvailabilityCheck.ViewModel) - func displayStoriesBlock(viewModel: Explore.StoriesVisibilityUpdate.ViewModel) func displayModuleErrorState(viewModel: Explore.CourseListStateUpdate.ViewModel) - func displayStatusBarStyle(viewModel: Explore.StatusBarStyleUpdate.ViewModel) func displaySearchCourses(viewModel: Explore.SearchCourses.ViewModel) func displayCourseListFilter(viewModel: Explore.CourseListFilterPresentation.ViewModel) func displaySearchResultsCourseListFilters(viewModel: Explore.SearchResultsCourseListFiltersUpdate.ViewModel) @@ -19,7 +17,6 @@ final class ExploreViewController: BaseExploreViewController { } static let submodulesOrder: [Explore.Submodule] = [ - .stories, .languageSwitch, .catalogBlocks, .visitedCourses @@ -29,7 +26,6 @@ final class ExploreViewController: BaseExploreViewController { private lazy var exploreInteractor = self.interactor as? ExploreInteractorProtocol private var currentContentLanguage: ContentLanguage? - private var currentStoriesSubmoduleState = StoriesState.shown // SearchResults private var searchResultsModuleInput: SearchResultsModuleInputProtocol? private var searchResultsController: UIViewController? @@ -125,13 +121,6 @@ final class ExploreViewController: BaseExploreViewController { } private func initLanguageDependentSubmodules(contentLanguage: ContentLanguage) { - // Stories - let shouldRefreshStories = self.currentStoriesSubmoduleState == .shown - || (self.currentStoriesSubmoduleState == .hidden && self.currentContentLanguage != contentLanguage) - if shouldRefreshStories { - self.refreshStateForStories(state: .shown) - } - // Catalog blocks let catalogBlocksAssembly = CatalogBlocksAssembly( contentLanguage: contentLanguage, @@ -150,40 +139,6 @@ final class ExploreViewController: BaseExploreViewController { self.currentContentLanguage = contentLanguage } - // MARK: Stories - - private enum StoriesState { - case shown - case hidden - } - - private func refreshStateForStories(state: StoriesState) { - switch state { - case .shown: - let storiesAssembly = StoriesAssembly( - output: self.exploreInteractor as? StoriesOutputProtocol - ) - let storiesViewController = storiesAssembly.makeModule() - let storiesContainerView = ExploreStoriesContainerView( - contentView: storiesViewController.view - ) - self.registerSubmodule( - .init( - viewController: storiesViewController, - view: storiesContainerView, - isLanguageDependent: true, - type: Explore.Submodule.stories - ) - ) - case .hidden: - if let submodule = self.getSubmodule(type: Explore.Submodule.stories) { - self.removeSubmodule(submodule) - } - } - - self.currentStoriesSubmoduleState = state - } - // MARK: - Visited courses submodule private enum VisitedCourseListState { @@ -330,10 +285,6 @@ extension ExploreViewController: ExploreViewControllerProtocol { ) } - func displayStoriesBlock(viewModel: Explore.StoriesVisibilityUpdate.ViewModel) { - self.refreshStateForStories(state: viewModel.isHidden ? .hidden : .shown) - } - func displayModuleErrorState(viewModel: Explore.CourseListStateUpdate.ViewModel) { switch viewModel.module { case .visitedCourses: @@ -347,12 +298,6 @@ extension ExploreViewController: ExploreViewControllerProtocol { } } - func displayStatusBarStyle(viewModel: Explore.StatusBarStyleUpdate.ViewModel) { - if let styledNavigationController = self.navigationController as? StyledNavigationController { - styledNavigationController.changeStatusBarStyle(viewModel.statusBarStyle, sender: self) - } - } - func displaySearchCourses(viewModel: Explore.SearchCourses.ViewModel) { self.searchBar.becomeFirstResponder() self.searchBarTextDidBeginEditing(self.searchBar) diff --git a/Stepic/Sources/Modules/Home/HomeDataFlow.swift b/Stepic/Sources/Modules/Home/HomeDataFlow.swift index b89b4e74cb..b90b9e30e4 100644 --- a/Stepic/Sources/Modules/Home/HomeDataFlow.swift +++ b/Stepic/Sources/Modules/Home/HomeDataFlow.swift @@ -6,6 +6,7 @@ enum Home { // MARK: Submodules identifiers enum Submodule: String, UniqueIdentifiable { + case stories case streakActivity case continueCourse case enrolledCourses @@ -74,4 +75,26 @@ enum Home { let result: State } } + + /// Update stories visibility + enum StoriesVisibilityUpdate { + struct Response { + let isHidden: Bool + } + + struct ViewModel { + let isHidden: Bool + } + } + + /// Update status bar style (called by stories module) + enum StatusBarStyleUpdate { + struct Response { + let statusBarStyle: UIStatusBarStyle + } + + struct ViewModel { + let statusBarStyle: UIStatusBarStyle + } + } } diff --git a/Stepic/Sources/Modules/Home/HomeInteractor.swift b/Stepic/Sources/Modules/Home/HomeInteractor.swift index 143f488d4c..109c54e0c1 100644 --- a/Stepic/Sources/Modules/Home/HomeInteractor.swift +++ b/Stepic/Sources/Modules/Home/HomeInteractor.swift @@ -88,6 +88,16 @@ final class HomeInteractor: BaseExploreInteractor, HomeInteractorProtocol { } } +extension HomeInteractor: StoriesOutputProtocol { + func hideStories() { + self.homePresenter?.presentStoriesBlock(response: .init(isHidden: true)) + } + + func handleStoriesStatusBarStyleUpdate(_ statusBarStyle: UIStatusBarStyle) { + self.homePresenter?.presentStatusBarStyle(response: .init(statusBarStyle: statusBarStyle)) + } +} + extension HomeInteractor: ContinueCourseOutputProtocol { func hideContinueCourse() { self.homePresenter?.presentCourseListState( diff --git a/Stepic/Sources/Modules/Home/HomePresenter.swift b/Stepic/Sources/Modules/Home/HomePresenter.swift index cb12f9b5b3..d504f3ad1a 100644 --- a/Stepic/Sources/Modules/Home/HomePresenter.swift +++ b/Stepic/Sources/Modules/Home/HomePresenter.swift @@ -4,6 +4,8 @@ protocol HomePresenterProtocol: BaseExplorePresenterProtocol { func presentStreakActivity(response: Home.StreakLoad.Response) func presentContent(response: Home.ContentLoad.Response) func presentCourseListState(response: Home.CourseListStateUpdate.Response) + func presentStoriesBlock(response: Home.StoriesVisibilityUpdate.Response) + func presentStatusBarStyle(response: Home.StatusBarStyleUpdate.Response) } final class HomePresenter: BaseExplorePresenter, HomePresenterProtocol { @@ -52,6 +54,14 @@ final class HomePresenter: BaseExplorePresenter, HomePresenterProtocol { ) } + func presentStoriesBlock(response: Home.StoriesVisibilityUpdate.Response) { + self.homeViewController?.displayStoriesBlock(viewModel: .init(isHidden: response.isHidden)) + } + + func presentStatusBarStyle(response: Home.StatusBarStyleUpdate.Response) { + self.homeViewController?.displayStatusBarStyle(viewModel: .init(statusBarStyle: response.statusBarStyle)) + } + private func makeStreakActivityMessage(days: Int, needsToSolveToday: Bool) -> String { let pluralizedDaysCnt = StringHelper.pluralize( number: days, diff --git a/Stepic/Sources/Modules/Home/HomeViewController.swift b/Stepic/Sources/Modules/Home/HomeViewController.swift index 64165192fe..c3158ced9c 100644 --- a/Stepic/Sources/Modules/Home/HomeViewController.swift +++ b/Stepic/Sources/Modules/Home/HomeViewController.swift @@ -5,6 +5,8 @@ protocol HomeViewControllerProtocol: BaseExploreViewControllerProtocol { func displayStreakInfo(viewModel: Home.StreakLoad.ViewModel) func displayContent(viewModel: Home.ContentLoad.ViewModel) func displayModuleErrorState(viewModel: Home.CourseListStateUpdate.ViewModel) + func displayStoriesBlock(viewModel: Home.StoriesVisibilityUpdate.ViewModel) + func displayStatusBarStyle(viewModel: Home.StatusBarStyleUpdate.ViewModel) } final class HomeViewController: BaseExploreViewController { @@ -14,6 +16,7 @@ final class HomeViewController: BaseExploreViewController { } fileprivate static let submodulesOrder: [Home.Submodule] = [ + .stories, .streakActivity, .continueCourse, .enrolledCourses, @@ -24,6 +27,7 @@ final class HomeViewController: BaseExploreViewController { private var lastContentLanguage: ContentLanguage? private var lastIsAuthorizedFlag: Bool = false + private var currentStoriesSubmoduleState = StoriesState.shown private var currentEnrolledCourseListState: EnrolledCourseListState? private lazy var streakView = StreakActivityView() @@ -75,6 +79,43 @@ final class HomeViewController: BaseExploreViewController { self.homeInteractor?.doContentLoad(request: .init()) } + // MARK: - Stories + + private enum StoriesState { + case shown + case hidden + } + + private func refreshStateForStories(state: StoriesState) { + defer { + self.currentStoriesSubmoduleState = state + } + + if let submodule = self.getSubmodule(type: Home.Submodule.stories) { + self.removeSubmodule(submodule) + } + + guard case .shown = state else { + return + } + + let storiesAssembly = StoriesAssembly( + output: self.homeInteractor as? StoriesOutputProtocol + ) + let storiesViewController = storiesAssembly.makeModule() + let storiesContainerView = ExploreStoriesContainerView( + contentView: storiesViewController.view + ) + self.registerSubmodule( + .init( + viewController: storiesViewController, + view: storiesContainerView, + isLanguageDependent: true, + type: Home.Submodule.stories + ) + ) + } + // MARK: - Streak activity private enum StreakActivityState { @@ -479,18 +520,32 @@ extension HomeViewController: HomeViewControllerProtocol { return } - strongSelf.lastContentLanguage = viewModel.contentLanguage - strongSelf.lastIsAuthorizedFlag = viewModel.isAuthorized - + let shouldDisplayStories = strongSelf.currentStoriesSubmoduleState == .shown + || (strongSelf.currentStoriesSubmoduleState == .hidden + && strongSelf.lastContentLanguage != viewModel.contentLanguage) let shouldDisplayContinueCourse = viewModel.isAuthorized let shouldDisplayAnonymousPlaceholder = !viewModel.isAuthorized + strongSelf.lastContentLanguage = viewModel.contentLanguage + strongSelf.lastIsAuthorizedFlag = viewModel.isAuthorized + + strongSelf.refreshStateForStories(state: shouldDisplayStories ? .shown : .hidden) strongSelf.refreshContinueCourse(state: shouldDisplayContinueCourse ? .shown : .hidden) strongSelf.refreshStateForEnrolledCourses(state: shouldDisplayAnonymousPlaceholder ? .anonymous : .normal) strongSelf.refreshStateForVisitedCourses(state: .shown) strongSelf.refreshStateForPopularCourses(state: .normal) } } + + func displayStoriesBlock(viewModel: Home.StoriesVisibilityUpdate.ViewModel) { + self.refreshStateForStories(state: viewModel.isHidden ? .hidden : .shown) + } + + func displayStatusBarStyle(viewModel: Home.StatusBarStyleUpdate.ViewModel) { + if let styledNavigationController = self.navigationController as? StyledNavigationController { + styledNavigationController.changeStatusBarStyle(viewModel.statusBarStyle, sender: self) + } + } } extension HomeViewController: BaseExploreViewDelegate { From e8257c99a145d25a4fffc5c3f586bc3fd6a3657f Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 26 Jan 2021 21:16:08 +0300 Subject: [PATCH 2/9] Sync personal offers on home content load --- .../Stories/Stories/StoriesAssembly.swift | 6 +++++- .../StoriesViewController.swift | 17 ++++++++++------- .../Modules/Explore/ExploreAssembly.swift | 4 ---- .../Modules/Explore/ExploreInteractor.swift | 11 ----------- Stepic/Sources/Modules/Home/HomeAssembly.swift | 5 ++++- .../Sources/Modules/Home/HomeInteractor.swift | 11 ++++++++++- .../Modules/Home/HomeViewController.swift | 1 + 7 files changed, 30 insertions(+), 25 deletions(-) diff --git a/Stepic/Legacy/Controllers/Stories/Stories/StoriesAssembly.swift b/Stepic/Legacy/Controllers/Stories/Stories/StoriesAssembly.swift index fb21390a9d..e857362d34 100644 --- a/Stepic/Legacy/Controllers/Stories/Stories/StoriesAssembly.swift +++ b/Stepic/Legacy/Controllers/Stories/Stories/StoriesAssembly.swift @@ -11,7 +11,10 @@ import UIKit final class StoriesAssembly: Assembly { weak var moduleOutput: StoriesOutputProtocol? - init(output: StoriesOutputProtocol?) { + private let storyOpenSource: StoryOpenSource + + init(storyOpenSource: StoryOpenSource, output: StoriesOutputProtocol? = nil) { + self.storyOpenSource = storyOpenSource self.moduleOutput = output } @@ -28,6 +31,7 @@ final class StoriesAssembly: Assembly { ) presenter.moduleOutput = self.moduleOutput viewController.presenter = presenter + viewController.storyOpenSource = self.storyOpenSource return viewController } diff --git a/Stepic/Legacy/Controllers/Stories/Stories/StoriesViewController/StoriesViewController.swift b/Stepic/Legacy/Controllers/Stories/Stories/StoriesViewController/StoriesViewController.swift index 62417523ad..46017fef81 100644 --- a/Stepic/Legacy/Controllers/Stories/Stories/StoriesViewController/StoriesViewController.swift +++ b/Stepic/Legacy/Controllers/Stories/Stories/StoriesViewController/StoriesViewController.swift @@ -13,6 +13,7 @@ final class StoriesViewController: UIViewController, ControllerWithStepikPlaceho var placeholderContainer = StepikPlaceholderControllerContainer() var presenter: StoriesPresenterProtocol? + var storyOpenSource = StoryOpenSource.catalog private var stories: [Story] = [] private var currentItemFrame: CGRect? @@ -97,23 +98,25 @@ final class StoriesViewController: UIViewController, ControllerWithStepikPlaceho }() func showStory(at index: Int) { - let moduleToPresent = OpenedStoriesAssembly( + let assembly = OpenedStoriesAssembly( stories: self.stories, startPosition: index, - storyOpenSource: .catalog, + storyOpenSource: self.storyOpenSource, moduleOutput: self.presenter as? OpenedStoriesOutputProtocol - ).makeModule() + ) + let viewController = assembly.makeModule() + if DeviceInfo.current.isPad { self.customPresentViewController( self.storyPresentr, - viewController: moduleToPresent, + viewController: viewController, animated: true, completion: nil ) } else { - moduleToPresent.modalPresentationStyle = .custom - moduleToPresent.transitioningDelegate = self - self.present(moduleToPresent, animated: true, completion: nil) + viewController.modalPresentationStyle = .custom + viewController.transitioningDelegate = self + self.present(viewController, animated: true, completion: nil) } } diff --git a/Stepic/Sources/Modules/Explore/ExploreAssembly.swift b/Stepic/Sources/Modules/Explore/ExploreAssembly.swift index 0d583fbc18..b189d0de9f 100644 --- a/Stepic/Sources/Modules/Explore/ExploreAssembly.swift +++ b/Stepic/Sources/Modules/Explore/ExploreAssembly.swift @@ -7,10 +7,6 @@ class ExploreAssembly: Assembly { presenter: presenter, contentLanguageService: ContentLanguageService(), networkReachabilityService: NetworkReachabilityService(), - userAccountService: UserAccountService(), - personalOffersService: PersonalOffersService( - storageRecordsNetworkService: StorageRecordsNetworkService(storageRecordsAPI: StorageRecordsAPI()) - ), languageSwitchAvailabilityService: ContentLanguageSwitchAvailabilityService() ) let viewController = ExploreViewController(interactor: interactor, analytics: StepikAnalytics.shared) diff --git a/Stepic/Sources/Modules/Explore/ExploreInteractor.swift b/Stepic/Sources/Modules/Explore/ExploreInteractor.swift index 61304c19dc..9ad71a9aec 100644 --- a/Stepic/Sources/Modules/Explore/ExploreInteractor.swift +++ b/Stepic/Sources/Modules/Explore/ExploreInteractor.swift @@ -11,8 +11,6 @@ protocol ExploreInteractorProtocol: BaseExploreInteractorProtocol { final class ExploreInteractor: BaseExploreInteractor, ExploreInteractorProtocol { private lazy var explorePresenter = self.presenter as? ExplorePresenterProtocol - private let userAccountService: UserAccountServiceProtocol - private let personalOffersService: PersonalOffersServiceProtocol private let contentLanguageSwitchAvailabilityService: ContentLanguageSwitchAvailabilityServiceProtocol private lazy var currentSearchResultsCourseListFilters = self.getDefaultSearchResultsCourseListFilters() @@ -21,12 +19,8 @@ final class ExploreInteractor: BaseExploreInteractor, ExploreInteractorProtocol presenter: ExplorePresenterProtocol, contentLanguageService: ContentLanguageServiceProtocol, networkReachabilityService: NetworkReachabilityServiceProtocol, - userAccountService: UserAccountServiceProtocol, - personalOffersService: PersonalOffersServiceProtocol, languageSwitchAvailabilityService: ContentLanguageSwitchAvailabilityServiceProtocol ) { - self.userAccountService = userAccountService - self.personalOffersService = personalOffersService self.contentLanguageSwitchAvailabilityService = languageSwitchAvailabilityService super.init( @@ -40,11 +34,6 @@ final class ExploreInteractor: BaseExploreInteractor, ExploreInteractorProtocol self.explorePresenter?.presentContent( response: .init(contentLanguage: self.contentLanguageService.globalContentLanguage) ) - - if self.networkReachabilityService.isReachable && self.userAccountService.isAuthorized, - let userID = self.userAccountService.currentUserID { - self.personalOffersService.syncPersonalOffers(userID: userID).cauterize() - } } func doLanguageSwitchBlockLoad(request: Explore.LanguageSwitchAvailabilityCheck.Request) { diff --git a/Stepic/Sources/Modules/Home/HomeAssembly.swift b/Stepic/Sources/Modules/Home/HomeAssembly.swift index a5002687b4..7628c3fd99 100644 --- a/Stepic/Sources/Modules/Home/HomeAssembly.swift +++ b/Stepic/Sources/Modules/Home/HomeAssembly.swift @@ -9,7 +9,10 @@ final class HomeAssembly: Assembly { provider: provider, userAccountService: UserAccountService(), networkReachabilityService: NetworkReachabilityService(), - contentLanguageService: ContentLanguageService() + contentLanguageService: ContentLanguageService(), + personalOffersService: PersonalOffersService( + storageRecordsNetworkService: StorageRecordsNetworkService(storageRecordsAPI: StorageRecordsAPI()) + ) ) let viewController = HomeViewController(interactor: interactor, analytics: StepikAnalytics.shared) diff --git a/Stepic/Sources/Modules/Home/HomeInteractor.swift b/Stepic/Sources/Modules/Home/HomeInteractor.swift index 109c54e0c1..574b1d279c 100644 --- a/Stepic/Sources/Modules/Home/HomeInteractor.swift +++ b/Stepic/Sources/Modules/Home/HomeInteractor.swift @@ -9,6 +9,7 @@ protocol HomeInteractorProtocol: BaseExploreInteractorProtocol { final class HomeInteractor: BaseExploreInteractor, HomeInteractorProtocol { private let provider: HomeProviderProtocol private let userAccountService: UserAccountServiceProtocol + private let personalOffersService: PersonalOffersServiceProtocol private lazy var homePresenter = self.presenter as? HomePresenterProtocol @@ -17,10 +18,13 @@ final class HomeInteractor: BaseExploreInteractor, HomeInteractorProtocol { provider: HomeProviderProtocol, userAccountService: UserAccountServiceProtocol, networkReachabilityService: NetworkReachabilityServiceProtocol, - contentLanguageService: ContentLanguageServiceProtocol + contentLanguageService: ContentLanguageServiceProtocol, + personalOffersService: PersonalOffersServiceProtocol ) { self.provider = provider self.userAccountService = userAccountService + self.personalOffersService = personalOffersService + super.init( presenter: presenter, contentLanguageService: contentLanguageService, @@ -55,6 +59,11 @@ final class HomeInteractor: BaseExploreInteractor, HomeInteractorProtocol { contentLanguage: self.contentLanguageService.globalContentLanguage ) ) + + if self.networkReachabilityService.isReachable && self.userAccountService.isAuthorized, + let userID = self.userAccountService.currentUserID { + self.personalOffersService.syncPersonalOffers(userID: userID).cauterize() + } } override func presentEmptyState(sourceModule: CourseListInputProtocol) { diff --git a/Stepic/Sources/Modules/Home/HomeViewController.swift b/Stepic/Sources/Modules/Home/HomeViewController.swift index c3158ced9c..bd5c5e320b 100644 --- a/Stepic/Sources/Modules/Home/HomeViewController.swift +++ b/Stepic/Sources/Modules/Home/HomeViewController.swift @@ -100,6 +100,7 @@ final class HomeViewController: BaseExploreViewController { } let storiesAssembly = StoriesAssembly( + storyOpenSource: .home, output: self.homeInteractor as? StoriesOutputProtocol ) let storiesViewController = storiesAssembly.makeModule() From fe646ce333da23429a8478b460e96948f60bc303 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 9 Feb 2021 18:08:15 +0300 Subject: [PATCH 3/9] New home continue learning widget (#895) * Update submodule registration * Update appearance * Fix contentInsets * Update loading skeleton * Handle empty & error states --- Stepic.xcodeproj/project.pbxproj | 38 +-- .../Continue Learning/Contents.json | 6 + .../Contents.json | 15 ++ .../continue_learning_arrow_right.pdf | Bin 0 -> 1288 bytes .../Contents.json | 15 ++ .../continue_learning_gradient.pdf | Bin 0 -> 2053 bytes .../Course_list_placeholder/Contents.json | 6 +- .../Contents.json | 20 +- .../Contents.json | 20 +- .../Contents.json | 20 +- .../Contents.json | 10 +- .../Contents.json | 10 +- .../Contents.json | 10 +- .../plus.imageset/Contents.json | 15 ++ Stepic/Images.xcassets/plus.imageset/plus.pdf | Bin 0 -> 4458 bytes .../CodeEditorPreviewView.swift | 2 +- ...rsonalDeadlineModeCollectionViewCell.swift | 2 +- .../QuizViewController.swift | 2 +- .../StringQuiz/StringQuizViewController.swift | 2 +- .../StoryPartViews/TextStoryView.swift | 2 +- .../StepikVideoPlayerViewController.swift | 6 +- .../Legacy/Extensions/UIViewExtensions.swift | 31 --- Stepic/Legacy/Views/AvatarImageView.swift | 4 +- ...ersonalDeadlinesSuggestionWidgetView.swift | 2 +- .../ContinueCourseSkeletonView.swift | 61 ++++- Stepic/Legacy/Views/StepikButton.swift | 2 +- .../Extensions/UIKit/UIViewExtensions.swift | 57 ++++ .../Modules/BaseExplore/BaseExploreView.swift | 15 +- .../BaseExploreViewController.swift | 27 +- .../Views/Widget/CourseWidgetStatsView.swift | 6 +- .../ContinueCourseAssembly.swift | 8 +- .../ContinueCourseDataFlow.swift | 4 +- .../ContinueCourseInteractor.swift | 15 +- .../ContinueCourseOutputProtocol.swift | 1 - .../ContinueCoursePresenter.swift | 18 +- .../ContinueCourseProvider.swift | 59 ++--- .../ContinueCourse/ContinueCourseView.swift | 71 ----- .../ContinueCourseViewController.swift | 80 ++++-- .../Views/ContinueCourseBackgroundView.swift | 63 +++++ .../Views/ContinueCourseEmptyView.swift | 101 +++++++ .../Views/ContinueCourseView.swift | 124 +++++++++ .../Views/ContinueLastStepView.swift | 157 +++++++++++ .../Sources/Modules/Home/HomeInteractor.swift | 11 +- .../Modules/Home/HomeViewController.swift | 31 ++- .../NewFreeAnswerQuizView.swift | 4 +- .../NewStringQuiz/NewStringQuizView.swift | 4 +- .../Services/ApplicationShortcutService.swift | 4 +- .../ContinueActionButton.swift | 0 .../ContinueLastStepView.swift | 248 ------------------ Stepic/en.lproj/Localizable.strings | 2 + Stepic/ru.lproj/Localizable.strings | 2 + 51 files changed, 860 insertions(+), 553 deletions(-) create mode 100644 Stepic/Images.xcassets/Continue Learning/Contents.json create mode 100644 Stepic/Images.xcassets/Continue Learning/continue_learning_arrow_right.imageset/Contents.json create mode 100644 Stepic/Images.xcassets/Continue Learning/continue_learning_arrow_right.imageset/continue_learning_arrow_right.pdf create mode 100644 Stepic/Images.xcassets/Continue Learning/continue_learning_gradient.imageset/Contents.json create mode 100644 Stepic/Images.xcassets/Continue Learning/continue_learning_gradient.imageset/continue_learning_gradient.pdf create mode 100644 Stepic/Images.xcassets/plus.imageset/Contents.json create mode 100644 Stepic/Images.xcassets/plus.imageset/plus.pdf delete mode 100644 Stepic/Legacy/Extensions/UIViewExtensions.swift create mode 100644 Stepic/Sources/Extensions/UIKit/UIViewExtensions.swift delete mode 100644 Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseView.swift create mode 100644 Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseBackgroundView.swift create mode 100644 Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseEmptyView.swift create mode 100644 Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseView.swift create mode 100644 Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueLastStepView.swift rename Stepic/Sources/Views/{ContinueLastStepView => }/ContinueActionButton.swift (100%) delete mode 100644 Stepic/Sources/Views/ContinueLastStepView/ContinueLastStepView.swift diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index d5ced1fa92..2b2874cd6b 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -234,7 +234,6 @@ 089877AD214047EE0065DFA2 /* Numbers+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089877A8214047EC0065DFA2 /* Numbers+Random.swift */; }; 089877B0214047EE0065DFA2 /* UserDefaults+StorageServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089877AB214047ED0065DFA2 /* UserDefaults+StorageServiceProtocol.swift */; }; 089877B221404CF10065DFA2 /* StringStorageServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089877B121404CF00065DFA2 /* StringStorageServiceProtocol.swift */; }; - 089A0DA71BE9FFCE004AF4EB /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A0DA61BE9FFCE004AF4EB /* UIViewExtensions.swift */; }; 08A0218E1D675B4700915679 /* SectionNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A0218D1D675B4700915679 /* SectionNavigationDelegate.swift */; }; 08A1256F1BDE8E460066B2B2 /* Sorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A1256E1BDE8E460066B2B2 /* Sorter.swift */; }; 08A125791BDEBCC90066B2B2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 08A1257B1BDEBCC90066B2B2 /* Localizable.strings */; }; @@ -519,6 +518,8 @@ 2C4436B321356D960084489C /* CourseSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088FD8161FB242B3008A2953 /* CourseSubscriber.swift */; }; 2C453398204D46E90061342A /* PinsMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C453397204D46E90061342A /* PinsMap.swift */; }; 2C45339A204D5DEE0061342A /* PinsMapSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C453399204D5DEE0061342A /* PinsMapSpec.swift */; }; + 2C456C3425D2C47100435A86 /* ContinueCourseEmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C456C3325D2C47100435A86 /* ContinueCourseEmptyView.swift */; }; + 2C456C3E25D2C52F00435A86 /* ContinueCourseBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C456C3D25D2C52F00435A86 /* ContinueCourseBackgroundView.swift */; }; 2C45C6EF254160A100AF24BF /* HTMLExtractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C45C6EE254160A100AF24BF /* HTMLExtractorTests.swift */; }; 2C47A164206284B1003E87EC /* NotificationRequestAlertContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C47A163206284B1003E87EC /* NotificationRequestAlertContext.swift */; }; 2C48BA0725727822009103B2 /* AuthorsCourseListCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C48BA0625727822009103B2 /* AuthorsCourseListCollectionViewDataSource.swift */; }; @@ -938,6 +939,7 @@ 2CF6E63225ADB92B00B5A703 /* StepicWidgetExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 2CF6E62525ADB92A00B5A703 /* StepicWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 2CF7E3342417C1A700B9188E /* UseCellularDataForDownloadsStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF7E3332417C1A700B9188E /* UseCellularDataForDownloadsStorageManager.swift */; }; 2CF9BD3C2538508800C2AFD2 /* PromiseKit+Retry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF9BD3B2538508800C2AFD2 /* PromiseKit+Retry.swift */; }; + 2CFA48A025D1828C00BECD32 /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CFA489F25D1828C00BECD32 /* UIViewExtensions.swift */; }; 2CFC5ABC228ADFC400B5248A /* StepsPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CFC5ABB228ADFC400B5248A /* StepsPersistenceService.swift */; }; 2CFDB1241F559F9A00B8035C /* AvatarImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CFDB1231F559F9A00B8035C /* AvatarImageView.swift */; }; 2CFE6A84255AA6DE00A43952 /* CatalogBlockContentItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CFE6A83255AA6DE00A43952 /* CatalogBlockContentItemTests.swift */; }; @@ -1799,7 +1801,6 @@ 089877AB214047ED0065DFA2 /* UserDefaults+StorageServiceProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+StorageServiceProtocol.swift"; sourceTree = ""; }; 089877B121404CF00065DFA2 /* StringStorageServiceProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringStorageServiceProtocol.swift; sourceTree = ""; }; 089877B521407AB50065DFA2 /* Model_profile_staff_v24.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_profile_staff_v24.xcdatamodel; sourceTree = ""; }; - 089A0DA61BE9FFCE004AF4EB /* UIViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewExtensions.swift; sourceTree = ""; }; 08A0218D1D675B4700915679 /* SectionNavigationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionNavigationDelegate.swift; sourceTree = ""; }; 08A1256E1BDE8E460066B2B2 /* Sorter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sorter.swift; sourceTree = ""; }; 08A1257A1BDEBCC90066B2B2 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; @@ -2123,6 +2124,8 @@ 2C434D4425B7367500854D6F /* WidgetContentProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetContentProvider.swift; sourceTree = ""; }; 2C453397204D46E90061342A /* PinsMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinsMap.swift; sourceTree = ""; }; 2C453399204D5DEE0061342A /* PinsMapSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinsMapSpec.swift; sourceTree = ""; }; + 2C456C3325D2C47100435A86 /* ContinueCourseEmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueCourseEmptyView.swift; sourceTree = ""; }; + 2C456C3D25D2C52F00435A86 /* ContinueCourseBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContinueCourseBackgroundView.swift; sourceTree = ""; }; 2C45C6EE254160A100AF24BF /* HTMLExtractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLExtractorTests.swift; sourceTree = ""; }; 2C46417A24CFD23400FB6696 /* Model_lessons_demo_access_v58.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_lessons_demo_access_v58.xcdatamodel; sourceTree = ""; }; 2C47A163206284B1003E87EC /* NotificationRequestAlertContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestAlertContext.swift; sourceTree = ""; }; @@ -2561,6 +2564,7 @@ 2CF6E62F25ADB92B00B5A703 /* Info-Production.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Production.plist"; sourceTree = ""; }; 2CF7E3332417C1A700B9188E /* UseCellularDataForDownloadsStorageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UseCellularDataForDownloadsStorageManager.swift; sourceTree = ""; }; 2CF9BD3B2538508800C2AFD2 /* PromiseKit+Retry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PromiseKit+Retry.swift"; sourceTree = ""; }; + 2CFA489F25D1828C00BECD32 /* UIViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtensions.swift; sourceTree = ""; }; 2CFC5ABB228ADFC400B5248A /* StepsPersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepsPersistenceService.swift; sourceTree = ""; }; 2CFDA8181FBB3CFD0098A441 /* Model_sections_with_course_id_v21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_sections_with_course_id_v21.xcdatamodel; sourceTree = ""; }; 2CFDB1231F559F9A00B8035C /* AvatarImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarImageView.swift; sourceTree = ""; }; @@ -3436,6 +3440,7 @@ 2C5240D22399257B000DDA13 /* UIStatusBarStyle+DarkContent.swift */, 62E9845643D7541307856023 /* UITableViewExtensions.swift */, 2C9776E924222AED0097AEFC /* UIView+TraitCollection.swift */, + 2CFA489F25D1828C00BECD32 /* UIViewExtensions.swift */, ); path = UIKit; sourceTree = ""; @@ -3788,6 +3793,17 @@ path = SocialProfile; sourceTree = ""; }; + 2C64388D25D17F480045D139 /* Views */ = { + isa = PBXGroup; + children = ( + 2C456C3D25D2C52F00435A86 /* ContinueCourseBackgroundView.swift */, + 2C456C3325D2C47100435A86 /* ContinueCourseEmptyView.swift */, + 62E98021576A447517397409 /* ContinueCourseView.swift */, + 62E98FC21463AD1C43A467BB /* ContinueLastStepView.swift */, + ); + path = Views; + sourceTree = ""; + }; 2C6B2F6121AE659600DEB6AC /* Extensions */ = { isa = PBXGroup; children = ( @@ -3803,7 +3819,6 @@ 08CA59E91BBD3D55008DC44D /* UILabelExtensions.swift */, 62E98D9D45E6DA6099AF1C0B /* UITableView+TableHeaderLayout.swift */, 62E982BB5246C2BBC5E88D06 /* UIView+FromNib.swift */, - 089A0DA61BE9FFCE004AF4EB /* UIViewExtensions.swift */, 089877AB214047ED0065DFA2 /* UserDefaults+StorageServiceProtocol.swift */, ); path = Extensions; @@ -5642,15 +5657,6 @@ path = AchievementBadgeView; sourceTree = ""; }; - 2CFF8FFE242A22DF00FD7311 /* ContinueLastStepView */ = { - isa = PBXGroup; - children = ( - 62E9807E3506DAF40179593F /* ContinueActionButton.swift */, - 62E98FC21463AD1C43A467BB /* ContinueLastStepView.swift */, - ); - path = ContinueLastStepView; - sourceTree = ""; - }; 2CFF8FFF242A22EB00FD7311 /* LoadingPaginationView */ = { isa = PBXGroup; children = ( @@ -7082,6 +7088,7 @@ isa = PBXGroup; children = ( 62E98EDC02AF16EA5BAA748A /* BounceButton.swift */, + 62E9807E3506DAF40179593F /* ContinueActionButton.swift */, 62E9888DC7DFE8CFE5C220B6 /* CourseCoverImageView.swift */, 2C381BEE25505EC90084AD90 /* CourseListFilterBarButtonItem.swift */, 62E9856B0B8FB44A5492EF67 /* CourseRatingView.swift */, @@ -7102,7 +7109,6 @@ 62E986AC65867C28C93D475E /* TableInputTextField.swift */, 2C06E09A2241045800AF4DA2 /* TableInputTextView.swift */, 62E988C4DC97F14EAC09BC41 /* TabSegmentedControlView.swift */, - 2CFF8FFE242A22DF00FD7311 /* ContinueLastStepView */, 2C11C9F024EFC80400A4647B /* Layouts */, 62E989892C62C2BC2B1A3741 /* TabBar */, ); @@ -7716,9 +7722,9 @@ 62E98B7A07FDA68E6CA47ECB /* ContinueCourseOutputProtocol.swift */, 62E98A6EFA74ACC7FF243E12 /* ContinueCoursePresenter.swift */, 62E9818650597467F6A1D814 /* ContinueCourseProvider.swift */, - 62E98021576A447517397409 /* ContinueCourseView.swift */, 62E98EC2E16550A310CE6CEF /* ContinueCourseViewController.swift */, 62E984C48E60011A6C45B243 /* ContinueCourseViewModel.swift */, + 2C64388D25D17F480045D139 /* Views */, ); path = ContinueCourse; sourceTree = ""; @@ -9028,7 +9034,6 @@ 08421BCB21764FC400E8A81B /* ActiveSplitTestsContainer.swift in Sources */, 2C85C6A522D38A3800FDBAFE /* VotesNetworkService.swift in Sources */, 2CDC9EFA24E4FD0D00916BAE /* CourseInfoTryForFreeButton.swift in Sources */, - 089A0DA71BE9FFCE004AF4EB /* UIViewExtensions.swift in Sources */, 2CD04084250F67D0004D284F /* ProcessedContent.swift in Sources */, 0828FF6E1BC5D177000AFEA7 /* Section+CoreDataProperties.swift in Sources */, 2CB9E8D01F839C8E0004E17F /* NotificationsTableViewCell.swift in Sources */, @@ -9212,6 +9217,7 @@ 0885F84E1BA837E200F2A188 /* ApiDataDownloader.swift in Sources */, 2C5E90842333DF2100288BE3 /* CodeLanguageSuggestionsService.swift in Sources */, 2CB9E8C41F7AA5CD0004E17F /* NotificationsPresenter.swift in Sources */, + 2C456C3425D2C47100435A86 /* ContinueCourseEmptyView.swift in Sources */, 2C8BCC222486540F00DFB009 /* ShowAllButton.swift in Sources */, 08F485A21C57987C000165AA /* NumberQuizViewController.swift in Sources */, 2C5DF1431FED2758003B1177 /* CardStepDelegate.swift in Sources */, @@ -9637,6 +9643,7 @@ 62E980B234CAE5A61B9E546C /* CourseInfoTabSyllabusView.swift in Sources */, 2C698C9524CEA90100979661 /* NewProfileCoverView.swift in Sources */, 62E9870EF451D397B27AB075 /* CourseInfoTabSyllabusCellStatsView.swift in Sources */, + 2C456C3E25D2C52F00435A86 /* ContinueCourseBackgroundView.swift in Sources */, 62E9893B98E774DA8141AD0F /* CourseInfoTabSyllabusCellView.swift in Sources */, 2C2F6E6024DD3E69000E8844 /* StepikAnalyticsEvents.swift in Sources */, 62E98C92B0827BEB4E28A8D9 /* CourseInfoTabSyllabusTableViewCell.swift in Sources */, @@ -9967,6 +9974,7 @@ 62E987CF11C3A59E26F3E888 /* StepPresenter.swift in Sources */, 62E984897A5BBA262FC4F5E2 /* StepProvider.swift in Sources */, 62E987EA4263A9FE06376867 /* StepView.swift in Sources */, + 2CFA48A025D1828C00BECD32 /* UIViewExtensions.swift in Sources */, 62E9888DF0D85074AFE0092C /* StepViewController.swift in Sources */, 62E986D61FF5B6B4692F3D7F /* StepViewModel.swift in Sources */, 62E98BFC923309B4590E58C3 /* StepInputProtocol.swift in Sources */, diff --git a/Stepic/Images.xcassets/Continue Learning/Contents.json b/Stepic/Images.xcassets/Continue Learning/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/Stepic/Images.xcassets/Continue Learning/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stepic/Images.xcassets/Continue Learning/continue_learning_arrow_right.imageset/Contents.json b/Stepic/Images.xcassets/Continue Learning/continue_learning_arrow_right.imageset/Contents.json new file mode 100644 index 0000000000..b81355b517 --- /dev/null +++ b/Stepic/Images.xcassets/Continue Learning/continue_learning_arrow_right.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "continue_learning_arrow_right.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Stepic/Images.xcassets/Continue Learning/continue_learning_arrow_right.imageset/continue_learning_arrow_right.pdf b/Stepic/Images.xcassets/Continue Learning/continue_learning_arrow_right.imageset/continue_learning_arrow_right.pdf new file mode 100644 index 0000000000000000000000000000000000000000..884ba2aba60e1fd2771e0e654e42eba7a8edee35 GIT binary patch literal 1288 zcmZvcOK;Oa5PsnAt5lcaBjwFo3BsZm(iC2F4TD>6gSSHu@HE0 z0lj&FR8T7TiEhO__rh5uRmrt5#swG=a&3%PimVN)f_o=~rUEc@BvUN{NXeDg&RPm4 zUq!`z2%19Jj$Q~VikgDRUXYZn3I<0nl%{hYttKxy5rP<`at_9PCoETHsywM;?!lHl zj;)iF&mnSZP444Jw^K?94Oxy?UxnFfa`wP#_JyUXJ&xosziCttb8WTHQ>`mO z?bq^XZr0R&tjS|qt^qE}mKc~Lnyg?#c{PgrHtVBmO1gV3i03vwFaeGob~!!48lwylJ{}aXm!Qr_QJm?F$SHq^R(SNs4 z2$xacmYZ#CS?b>VIhEtQYj*Jh&Nr)zLs5!l)AW!M-WRyI+x(3b{r;a-w*5XG`de6b JcJ}u3?LSHL7(D<0 literal 0 HcmV?d00001 diff --git a/Stepic/Images.xcassets/Continue Learning/continue_learning_gradient.imageset/Contents.json b/Stepic/Images.xcassets/Continue Learning/continue_learning_gradient.imageset/Contents.json new file mode 100644 index 0000000000..c23d1304c6 --- /dev/null +++ b/Stepic/Images.xcassets/Continue Learning/continue_learning_gradient.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "continue_learning_gradient.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Stepic/Images.xcassets/Continue Learning/continue_learning_gradient.imageset/continue_learning_gradient.pdf b/Stepic/Images.xcassets/Continue Learning/continue_learning_gradient.imageset/continue_learning_gradient.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dea47dda74506db2fdb78772967513a5b5b09836 GIT binary patch literal 2053 zcma)7OK;mS48H4E@KT^XB(~%?Py|?$b{Mu|S+d)(gKDdZhs4TkXUJCczmJkF*-pK+ zIwaI5lH%)=FP4kD37tlSP;fy${2&0=*Wi2-#irf@LvV!OPiO7@-mx|sxAfJlPAE766!@P0C7sUYR700CUzIQ zUc#DPc06=pZEARG;DbDj2m|Hn_y7|XOBDMa?2o(dR-R=e-jxCBv&avq(Qo4VuRcve zH#YiB3u4bBW?s5I!KpQy4&L=)Yqsk=jA4EAEqNpb`9elWsw&Wslrhg1 zE3~L0>ARwkHS(h!U|ar;Xln1=i!A3iQjyk%VujI^jH{KXLZkUe;% zYMKF;yhIG57V5QDK}`-S+K;*BvPxlPUI3i$Bihn7UNb)%Q6bXv6c0Q#YI9!mo!qph zOO8fkL7YnQNL)xr^ZC+45e9)50K)B}E@%m``^^eGWQ|%qD8Nz-AWCCw(-87Qtj!q| zW6nb;49{<*k=Gg4CC_)FB&uv57TCSZO76uKT;|zTQ#xlQWeut=5O=jZE zWOp`Fg{lwYgDkkvhef*BRj^nD@o6jeK?T85)K(C+D)dR(`c$eGt>?~9lHI++n`Jcy2hvhodO)@ud^jPy};WpMD;tQkd+aTG^VxQ7{z{#+VGN6^j~h zu1FTmQCU|g%4Cpf8C0qwd+ESJUfaNLeC_Je;k#b_YuiQn!p6mwV{d%<(51guxY$?6 z7FHHAU!?3aTV8p4?Cj>r?VIhvBP%~Y`qH60PF~-={9SM9;mZ%~IC9^WsilE(&(6{F z@h8U`RZ_k%_~R`fZQZ~1*Hh2-@9aHy@4`E$XI}3q-;aLy=e@;QZTZkA6XnIX-hbrU zZ&!}y3R9miZ8&#gZ2$4gp$}F+{F?vXHuv3`=-;yU#c$t4pIzLyZPOWPc_^~F{oP-< z!{+(Nub=uUYiv4eiaUBVX%iz77SU@|dZ zqnM0KWr_oscjAsq2GuNh)aNBn6;&G4ZTt+FI2?eOFl!7E2+Rhbq>2AR?AgcPW$*A1DPnBh@q>x)(F z4_B)%lRZnuRKTYy+wfN?`t>N1RDpDd|{j`=Vj4f7oCo3X4~k^s11 zEgMp8MwQL5Zj}4`okUp}R!h~j93!SLmlQaG=L9an4MZZM5aMD%j_YsBInb8VEjlY% zQbjL2(HJqNM@5T3+n^=H;W*-&2T&3fqqu~9TTB41*nzdCEy4j!>ujP3n5?sLJPhDE zn+UzHx5eT&`o%e*!GG2=B(*{fXS)~G28EIt83kRpFl*eMm{F~yV}`v$rfErr<#ZU2 ziYO2m+B=K_gS4z_L$+F)(DlkwH3h&$Ew&JGP}hO1r=@B`GWro|@#mXb__Z)c-Uje7 zcXomgILT8>f>aWY`^9-Xh^^g8^8@&SXI)&$!MO-*Rpg*eXL>7V&jPkxp7cVD8XGBl(Lg{wN{0yuwAWa zX3|avC3rQ^BwRBy34)WMl#+$qu;&7+bkLjGxw*OUoXCci@QFl%Z~_s85O{>ldCijS zkY@Hm1caR!Icmy=T4TeESxjkKuUqM05GZuBD{?b(t!`ADxQa|@g;v=+fxwnw1!0_0 zE=+BPV>3zThm}WEC~z@{}Sh7cAMNxC_rSMb?CI@3j^A-wUs`VWOE~2g0zd zR|(vJsztAASS9^2YF4+#2uL~QuBpN`0RzAVX)@;a3O(J_gMoI_k8YBVAnwA|g{vb3 zI&$t#R~N315a`IcJ6&D4Izpf$=l^HATHo66^_~vSu`g-3TC2D{NyYo}2^36AQpK?7 z*?Im(X~gpbphSJi<6LmOAc98lZ7mrD@_@;p1Zvknq)h;UZlSZ7DIZV^fgw#= submodule.type.position { - self.exploreView?.insertBlockView( - submodule.view, - before: module.view - ) - return + if submodule.isArrangeable { + let arrangeableSubmodules = self.submodules.filter(\.isArrangeable) + // We have contract here: + // - subviews in exploreView have same position as in corresponding Submodule object + for module in arrangeableSubmodules where module.type.position >= submodule.type.position { + self.exploreView?.insertBlockView( + submodule.view, + before: module.view + ) + return + } + } else { + self.exploreView?.addSubview(submodule.view) } } @@ -71,6 +80,7 @@ class BaseExploreViewController: UIViewController { } func removeSubmodule(_ submodule: Submodule) { + submodule.viewController?.willMove(toParent: nil) self.exploreView?.removeBlockView(submodule.view) submodule.viewController?.removeFromParent() self.submodules = self.submodules.filter { submodule.view != $0.view } @@ -124,6 +134,7 @@ class BaseExploreViewController: UIViewController { struct Submodule { let viewController: UIViewController? let view: UIView + var isArrangeable: Bool = true let isLanguageDependent: Bool let type: SubmoduleType } diff --git a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetStatsView.swift b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetStatsView.swift index a83d6c4f8a..e946f552af 100644 --- a/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetStatsView.swift +++ b/Stepic/Sources/Modules/CourseList/Views/Widget/CourseWidgetStatsView.swift @@ -4,7 +4,7 @@ import UIKit extension CourseWidgetStatsView { struct Appearance { let statItemsSpacing: CGFloat = 8 - let leftInset: CGFloat = 2.0 + var leftInset: CGFloat = 2.0 let learnersViewImageViewSize = CGSize(width: 8, height: 10) let ratingViewImageViewSize = CGSize(width: 8, height: 12) @@ -134,6 +134,10 @@ final class CourseWidgetStatsView: UIView { fatalError("init(coder:) has not been implemented") } + func hideAllItems() { + self.stackView.arrangedSubviews.forEach { $0.isHidden = true } + } + func updateProgress(viewModel: CourseWidgetProgressViewModel) { let progressPie = ProgressCircleImage( progress: viewModel.progress, diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseAssembly.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseAssembly.swift index bf4bb23898..3cd1727d2e 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseAssembly.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseAssembly.swift @@ -9,11 +9,9 @@ final class ContinueCourseAssembly: Assembly { func makeModule() -> UIViewController { let provider = ContinueCourseProvider( - userCoursesAPI: UserCoursesAPI(), - coursesAPI: CoursesAPI(), - progressesNetworkService: ProgressesNetworkService( - progressesAPI: ProgressesAPI() - ) + userCoursesNetworkService: UserCoursesNetworkService(userCoursesAPI: UserCoursesAPI()), + coursesNetworkService: CoursesNetworkService(coursesAPI: CoursesAPI()), + progressesNetworkService: ProgressesNetworkService(progressesAPI: ProgressesAPI()) ) let presenter = ContinueCoursePresenter() diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift index e854fe4132..f4cb58ab58 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift @@ -8,7 +8,7 @@ enum ContinueCourse { struct Request {} struct Response { - let result: Course + let result: StepikResult } struct ViewModel { @@ -38,6 +38,8 @@ enum ContinueCourse { enum ViewControllerState { case loading + case empty + case error case result(data: ContinueCourseViewModel) } } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift index 0f6d5af688..e9e9f0dd71 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift @@ -41,12 +41,14 @@ final class ContinueCourseInteractor: ContinueCourseInteractorProtocol { self.provider.fetchLastCourse().done { course in if let course = course { self.currentCourse = course - self.presenter.presentLastCourse(response: .init(result: course)) + self.presenter.presentLastCourse(response: .init(result: .success(course))) } else { - self.moduleOutput?.hideContinueCourse() + self.presenter.presentLastCourse(response: .init(result: .failure(Error.noLastCourse))) } }.catch { _ in - self.moduleOutput?.hideContinueCourse() + if self.currentCourse == nil { + self.presenter.presentLastCourse(response: .init(result: .failure(Error.fetchFailed))) + } } } @@ -71,6 +73,11 @@ final class ContinueCourseInteractor: ContinueCourseInteractorProtocol { ) self.tooltipStorageManager.didShowOnHomeContinueLearning = true } + + enum Error: Swift.Error { + case fetchFailed + case noLastCourse + } } extension ContinueCourseInteractor: DataBackUpdateServiceDelegate { @@ -85,7 +92,7 @@ extension ContinueCourseInteractor: DataBackUpdateServiceDelegate { } self.currentCourse = course - self.presenter.presentLastCourse(response: .init(result: course)) + self.presenter.presentLastCourse(response: .init(result: .success(course))) } func dataBackUpdateService( diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift index 43c6f6cd90..f62f971190 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift @@ -1,6 +1,5 @@ import Foundation protocol ContinueCourseOutputProtocol: AnyObject { - func hideContinueCourse() func presentLastStep(course: Course, isAdaptive: Bool, viewSource: AnalyticsEvent.CourseViewSource) } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCoursePresenter.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCoursePresenter.swift index c540438f44..ef4893f159 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCoursePresenter.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCoursePresenter.swift @@ -9,13 +9,17 @@ final class ContinueCoursePresenter: ContinueCoursePresenterProtocol { weak var viewController: ContinueCourseViewControllerProtocol? func presentLastCourse(response: ContinueCourse.LastCourseLoad.Response) { - var viewModel: ContinueCourse.LastCourseLoad.ViewModel - - viewModel = ContinueCourse.LastCourseLoad.ViewModel( - state: .result(data: self.makeViewModel(course: response.result)) - ) - - self.viewController?.displayLastCourse(viewModel: viewModel) + switch response.result { + case .success(let course): + let viewModel = self.makeViewModel(course: course) + self.viewController?.displayLastCourse(viewModel: .init(state: .result(data: viewModel))) + case .failure(let error): + if case ContinueCourseInteractor.Error.noLastCourse = error { + self.viewController?.displayLastCourse(viewModel: .init(state: .empty)) + } else { + self.viewController?.displayLastCourse(viewModel: .init(state: .error)) + } + } } func presentTooltip(response: ContinueCourse.TooltipAvailabilityCheck.Response) { diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseProvider.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseProvider.swift index d91e7454fa..6bc97c9a1e 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseProvider.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseProvider.swift @@ -6,49 +6,44 @@ protocol ContinueCourseProviderProtocol { } final class ContinueCourseProvider: ContinueCourseProviderProtocol { - private let userCoursesAPI: UserCoursesAPI - private let coursesAPI: CoursesAPI + private let userCoursesNetworkService: UserCoursesNetworkServiceProtocol + private let coursesNetworkService: CoursesNetworkServiceProtocol private let progressesNetworkService: ProgressesNetworkServiceProtocol init( - userCoursesAPI: UserCoursesAPI, - coursesAPI: CoursesAPI, + userCoursesNetworkService: UserCoursesNetworkServiceProtocol, + coursesNetworkService: CoursesNetworkServiceProtocol, progressesNetworkService: ProgressesNetworkServiceProtocol ) { - self.userCoursesAPI = userCoursesAPI - self.coursesAPI = coursesAPI + self.userCoursesNetworkService = userCoursesNetworkService + self.coursesNetworkService = coursesNetworkService self.progressesNetworkService = progressesNetworkService } func fetchLastCourse() -> Promise { - Promise { seal in - self.userCoursesAPI.retrieve(page: 1).then { result -> Promise<[Course]> in - let lastCourse = result.0 - .sorted(by: { $0.lastViewed > $1.lastViewed }) - .prefix(1) - // [] or [id] - let coursesIDs = lastCourse.compactMap { $0.courseID } - return self.coursesAPI.retrieve(ids: coursesIDs) - }.then { courses -> Promise<(Course?, Progress?)> in - if let course = courses.first, - let progressID = course.progressId { - return self.progressesNetworkService - .fetch(id: progressID) - .map { (course, $0) } - } else { - return Promise.value((nil, nil)) - } - }.done { course, progress in - course?.progress = progress + self.userCoursesNetworkService.fetch().then { userCoursesFetchResult -> Promise<[Course]> in + let lastCourse = userCoursesFetchResult.0 + .sorted(by: { $0.lastViewed > $1.lastViewed }) + .prefix(1) + // [] or [id] + let coursesIDs = lastCourse.compactMap { $0.courseID } + return self.coursesNetworkService.fetch(ids: coursesIDs) + }.then { courses -> Promise<(Course?, Progress?)> in + if let course = courses.first, + let progressID = course.progressId { + return self.progressesNetworkService + .fetch(id: progressID) + .map { (course, $0) } + } else { + return .value((nil, nil)) + } + }.then { course, progress -> Promise in + if let course = course { + course.progress = progress CoreDataHelper.shared.save() - seal.fulfill(course) - }.catch { error in - seal.reject(error) } - } - } - enum Error: Swift.Error { - case lastCourseFetchFailed + return .value(course) + } } } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseView.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseView.swift deleted file mode 100644 index 5b43c3fa0b..0000000000 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseView.swift +++ /dev/null @@ -1,71 +0,0 @@ -import SnapKit -import UIKit - -protocol ContinueCourseViewDelegate: AnyObject { - func continueCourseContinueButtonDidClick(_ continueCourseView: ContinueCourseView) -} - -final class ContinueCourseView: UIView { - private lazy var lastStepView = ContinueLastStepView() - weak var delegate: ContinueCourseViewDelegate? - - // View for tooltip - var tooltipAnchorView: UIView { self.lastStepView.continueButton } - - override init(frame: CGRect) { - super.init(frame: frame) - - self.setupView() - self.addSubviews() - self.makeConstraints() - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configure(viewModel: ContinueCourseViewModel) { - self.lastStepView.courseTitle = viewModel.title - - if let progressDescription = viewModel.progress?.description, - let progressValue = viewModel.progress?.value { - self.lastStepView.progressText = progressDescription - self.lastStepView.progress = progressValue - } - self.lastStepView.coverImageURL = viewModel.coverImageURL - } - - func showLoading() { - self.skeleton.viewBuilder = { - ContinueCourseSkeletonView() - } - self.skeleton.show() - } - - func hideLoading() { - self.skeleton.hide() - } -} - -extension ContinueCourseView: ProgrammaticallyInitializableViewProtocol { - func setupView() { - self.lastStepView.onContinueButtonClick = { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.delegate?.continueCourseContinueButtonDidClick(strongSelf) - } - } - - func addSubviews() { - self.addSubview(self.lastStepView) - } - - func makeConstraints() { - self.lastStepView.translatesAutoresizingMaskIntoConstraints = false - self.lastStepView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - } -} diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift index 16e5fdb29a..e598a19297 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift @@ -5,14 +5,13 @@ protocol ContinueCourseViewControllerProtocol: AnyObject { func displayTooltip(viewModel: ContinueCourse.TooltipAvailabilityCheck.ViewModel) } -final class ContinueCourseViewController: UIViewController { +final class ContinueCourseViewController: UIViewController, ControllerWithStepikPlaceholder { private let interactor: ContinueCourseInteractorProtocol - private var state: ContinueCourse.ViewControllerState { - didSet { - self.updateState() - } - } + private var state: ContinueCourse.ViewControllerState + var placeholderContainer = StepikPlaceholderControllerContainer( + appearance: .init(placeholderAppearance: .init(backgroundColor: .clear)) + ) lazy var continueCourseView = self.view as? ContinueCourseView private lazy var continueLearningTooltip = TooltipFactory.continueLearningWidget @@ -34,9 +33,7 @@ final class ContinueCourseViewController: UIViewController { // MARK: ViewController lifecycle override func loadView() { - let view = ContinueCourseView( - frame: UIScreen.main.bounds - ) + let view = ContinueCourseView(frame: UIScreen.main.bounds) view.delegate = self self.view = view } @@ -44,31 +41,62 @@ final class ContinueCourseViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - self.updateState() + self.registerPlaceholder( + placeholder: StepikPlaceholder( + .tryAgain, + action: { [weak self] in + guard let strongSelf = self else { + return + } + + strongSelf.updateState(newState: .loading) + strongSelf.interactor.doLastCourseRefresh(request: .init()) + } + ), + for: .connectionError + ) + + self.updateState(newState: self.state) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) self.interactor.doLastCourseRefresh(request: .init()) } - private func updateState() { - if case .loading = self.state { + private func updateState(newState: ContinueCourse.ViewControllerState) { + defer { + self.state = newState + } + + self.isPlaceholderShown = false + self.continueCourseView?.hideLoading() + self.continueCourseView?.hideEmpty() + self.continueCourseView?.hideError() + + switch newState { + case .loading: self.continueCourseView?.showLoading() - } else { - self.continueCourseView?.hideLoading() + case .empty: + self.continueCourseView?.showEmpty() + case .error: + self.continueCourseView?.showError() + self.showPlaceholder(for: .connectionError) + case .result(let viewModel): + self.continueCourseView?.configure(viewModel: viewModel) + self.interactor.doTooltipAvailabilityCheck(request: .init()) } } } extension ContinueCourseViewController: ContinueCourseViewControllerProtocol { func displayLastCourse(viewModel: ContinueCourse.LastCourseLoad.ViewModel) { - if case .result(let result) = viewModel.state { - self.continueCourseView?.configure(viewModel: result) - self.interactor.doTooltipAvailabilityCheck(request: .init()) - } - - self.state = viewModel.state + self.updateState(newState: viewModel.state) } func displayTooltip(viewModel: ContinueCourse.TooltipAvailabilityCheck.ViewModel) { - guard let continueCourseView = self.continueCourseView else { + guard let continueCourseView = self.continueCourseView, + let parentView = self.parent?.view else { return } @@ -78,8 +106,8 @@ extension ContinueCourseViewController: ContinueCourseViewControllerProtocol { continueCourseView.setNeedsLayout() continueCourseView.layoutIfNeeded() self?.continueLearningTooltip.show( - direction: .up, - in: continueCourseView, + direction: .down, + in: parentView, from: continueCourseView.tooltipAnchorView ) } @@ -88,7 +116,11 @@ extension ContinueCourseViewController: ContinueCourseViewControllerProtocol { } extension ContinueCourseViewController: ContinueCourseViewDelegate { - func continueCourseContinueButtonDidClick(_ continueCourseView: ContinueCourseView) { + func continueCourseDidClickContinue(_ continueCourseView: ContinueCourseView) { self.interactor.doContinueLastCourseAction(request: .init()) } + + func continueCourseDidClickEmpty(_ continueCourseView: ContinueCourseView) { + DeepLinkRouter.routeToCatalog() + } } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseBackgroundView.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseBackgroundView.swift new file mode 100644 index 0000000000..c92fb9b2be --- /dev/null +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseBackgroundView.swift @@ -0,0 +1,63 @@ +import SnapKit +import UIKit + +extension ContinueCourseBackgroundView { + struct Appearance { + let backgroundColor = UIColor.stepikSecondaryBackground + } +} + +final class ContinueCourseBackgroundView: UIView { + let appearance: Appearance + + private lazy var imageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "continue_learning_gradient")) + imageView.contentMode = .scaleAspectFill + return imageView + }() + + init( + frame: CGRect = .zero, + appearance: Appearance = Appearance() + ) { + self.appearance = appearance + super.init(frame: frame) + + self.setupView() + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + self.performBlockIfAppearanceChanged(from: previousTraitCollection) { + self.updateAppearance() + } + } + + private func updateAppearance() { + self.backgroundColor = self.appearance.backgroundColor + self.imageView.isHidden = self.isDarkInterfaceStyle + } +} + +extension ContinueCourseBackgroundView: ProgrammaticallyInitializableViewProtocol { + func setupView() { + self.updateAppearance() + } + + func addSubviews() { + self.addSubview(self.imageView) + } + + func makeConstraints() { + self.imageView.translatesAutoresizingMaskIntoConstraints = false + self.imageView.snp.makeConstraints { $0.edges.equalToSuperview() } + } +} diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseEmptyView.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseEmptyView.swift new file mode 100644 index 0000000000..f9945065ff --- /dev/null +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseEmptyView.swift @@ -0,0 +1,101 @@ +import SnapKit +import UIKit + +extension ContinueCourseEmptyView { + struct Appearance { + let primaryColor: UIColor + let defaultInsets = LayoutInsets.default + + let plusIconSize = CGSize(width: 20, height: 20) + let plusContainerCornerRadius: CGFloat = 8 + let plusContainerSize = CGSize(width: 40, height: 40) + var plusContainerBackgroundColor: UIColor { + .dynamic(light: self.primaryColor.withAlphaComponent(0.12), dark: .stepikTertiaryBackground) + } + + let titleFont = UIFont.systemFont(ofSize: 16, weight: .medium) + let titleInsets = LayoutInsets(left: 8) + } +} + +final class ContinueCourseEmptyView: UIControl { + let appearance: Appearance + + private lazy var plusIconImageView: UIImageView = { + let image = UIImage(named: "plus")?.withRenderingMode(.alwaysTemplate) + let imageView = UIImageView(image: image) + imageView.tintColor = self.appearance.primaryColor + imageView.contentMode = .scaleAspectFit + return imageView + }() + private lazy var plusIconContainerView = UIView() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = self.appearance.primaryColor + label.font = self.appearance.titleFont + label.numberOfLines = 1 + label.text = NSLocalizedString("ContinueCourseEmptyTitle", comment: "") + return label + }() + + override var isHighlighted: Bool { + didSet { + self.plusIconImageView.alpha = self.isHighlighted ? 0.5 : 1.0 + self.titleLabel.alpha = self.isHighlighted ? 0.5 : 1.0 + } + } + + init(frame: CGRect = .zero, appearance: Appearance) { + self.appearance = appearance + super.init(frame: frame) + + self.setupView() + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension ContinueCourseEmptyView: ProgrammaticallyInitializableViewProtocol { + func setupView() { + self.plusIconContainerView.backgroundColor = self.appearance.plusContainerBackgroundColor + self.plusIconContainerView.roundAllCorners(radius: self.appearance.plusContainerCornerRadius) + } + + func addSubviews() { + self.addSubview(self.plusIconContainerView) + self.plusIconContainerView.addSubview(self.plusIconImageView) + + self.addSubview(self.titleLabel) + } + + func makeConstraints() { + self.plusIconContainerView.translatesAutoresizingMaskIntoConstraints = false + self.plusIconContainerView.snp.makeConstraints { make in + make.leading + .equalTo(self.safeAreaLayoutGuide.snp.leading) + .offset(self.appearance.defaultInsets.left) + make.centerY.equalToSuperview() + make.size.equalTo(self.appearance.plusContainerSize) + } + + self.plusIconImageView.translatesAutoresizingMaskIntoConstraints = false + self.plusIconImageView.snp.makeConstraints { make in + make.size.equalTo(self.appearance.plusIconSize) + make.center.equalToSuperview() + } + + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.titleLabel.snp.makeConstraints { make in + make.leading + .equalTo(self.plusIconContainerView.snp.trailing) + .offset(self.appearance.titleInsets.left) + make.centerY.equalTo(self.plusIconContainerView.snp.centerY) + } + } +} diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseView.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseView.swift new file mode 100644 index 0000000000..f7baff58b1 --- /dev/null +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseView.swift @@ -0,0 +1,124 @@ +import SnapKit +import UIKit + +protocol ContinueCourseViewDelegate: AnyObject { + func continueCourseDidClickContinue(_ continueCourseView: ContinueCourseView) + func continueCourseDidClickEmpty(_ continueCourseView: ContinueCourseView) +} + +extension ContinueCourseView { + struct Appearance { + let cornerRadius: CGFloat = 13 + let primaryColor = UIColor.dynamic(light: .stepikVioletFixed, dark: .stepikSystemPrimaryText) + } +} + +final class ContinueCourseView: UIView { + weak var delegate: ContinueCourseViewDelegate? + + let appearance: Appearance + + private lazy var backgroundView = ContinueCourseBackgroundView() + + private lazy var emptyView: ContinueCourseEmptyView = { + let view = ContinueCourseEmptyView(appearance: .init(primaryColor: self.appearance.primaryColor)) + view.addTarget(self, action: #selector(self.emptyViewClicked), for: .touchUpInside) + view.isHidden = true + return view + }() + + private lazy var lastStepView: ContinueLastStepView = { + let view = ContinueLastStepView(appearance: .init(primaryColor: self.appearance.primaryColor)) + view.addTarget(self, action: #selector(self.lastStepViewClicked), for: .touchUpInside) + return view + }() + + var tooltipAnchorView: UIView { self.lastStepView.tooltipAnchorView } + + init(frame: CGRect, appearance: Appearance = Appearance()) { + self.appearance = appearance + super.init(frame: frame) + + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + self.roundCorners([.topLeft, .topRight], radius: self.appearance.cornerRadius) + } + + func configure(viewModel: ContinueCourseViewModel) { + self.lastStepView.courseTitle = viewModel.title + + if let progressDescription = viewModel.progress?.description, + let progressValue = viewModel.progress?.value { + self.lastStepView.progressText = progressDescription + self.lastStepView.progress = progressValue + } + self.lastStepView.coverImageURL = viewModel.coverImageURL + } + + func showLoading() { + self.lastStepView.isHidden = true + self.skeleton.viewBuilder = { ContinueCourseSkeletonView() } + self.skeleton.show() + } + + func hideLoading() { + self.lastStepView.isHidden = false + self.skeleton.hide() + } + + func showEmpty() { + self.lastStepView.isHidden = true + self.emptyView.isHidden = false + } + + func hideEmpty() { + self.lastStepView.isHidden = false + self.emptyView.isHidden = true + } + + func showError() { + self.lastStepView.isHidden = true + } + + func hideError() { + self.lastStepView.isHidden = false + } + + @objc + private func lastStepViewClicked() { + self.delegate?.continueCourseDidClickContinue(self) + } + + @objc + private func emptyViewClicked() { + self.delegate?.continueCourseDidClickEmpty(self) + } +} + +extension ContinueCourseView: ProgrammaticallyInitializableViewProtocol { + func addSubviews() { + self.addSubview(self.backgroundView) + self.addSubview(self.emptyView) + self.addSubview(self.lastStepView) + } + + func makeConstraints() { + [ + self.backgroundView, + self.emptyView, + self.lastStepView + ].forEach { view in + view.translatesAutoresizingMaskIntoConstraints = false + view.snp.makeConstraints { $0.edges.equalToSuperview() } + } + } +} diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueLastStepView.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueLastStepView.swift new file mode 100644 index 0000000000..170fd09af8 --- /dev/null +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueLastStepView.swift @@ -0,0 +1,157 @@ +import SnapKit +import UIKit + +extension ContinueLastStepView { + struct Appearance { + let primaryColor: UIColor + let defaultInsets = LayoutInsets.default + + let coverCornerRadius: CGFloat = 8 + let coverSize = CGSize(width: 40, height: 40) + + let courseLabelFont = UIFont.systemFont(ofSize: 16, weight: .medium) + let courseLabelInsets = LayoutInsets(left: 8, right: 8) + + let statsViewHeight: CGFloat = 17 + let progressFillColor = UIColor.stepikGreenFixed + let progressLabelTextColor = UIColor.white + + let rightDetailImageSize = CGSize(width: 20, height: 30) + } +} + +final class ContinueLastStepView: UIControl { + let appearance: Appearance + + private lazy var coverImageView: CourseCoverImageView = { + let view = CourseCoverImageView() + view.clipsToBounds = true + view.layer.cornerRadius = self.appearance.coverCornerRadius + return view + }() + + private lazy var courseNameLabel: UILabel = { + let label = UILabel() + label.textColor = self.appearance.primaryColor + label.font = self.appearance.courseLabelFont + label.numberOfLines = 1 + return label + }() + + private lazy var statsView: CourseWidgetStatsView = { + let appearance = CourseWidgetStatsView.Appearance( + leftInset: 0, + imagesRenderingBackgroundColor: self.appearance.primaryColor, + imagesRenderingTintColor: self.appearance.progressFillColor, + itemTextColor: self.appearance.primaryColor, + itemImageTintColor: self.appearance.primaryColor + ) + let view = CourseWidgetStatsView(appearance: appearance) + view.hideAllItems() + return view + }() + + private lazy var rightDetailImageView: UIImageView = { + let image = UIImage(named: "continue_learning_arrow_right")?.withRenderingMode(.alwaysTemplate) + let imageView = UIImageView(image: image) + imageView.tintColor = self.appearance.primaryColor + imageView.contentMode = .scaleAspectFit + return imageView + }() + + var courseTitle: String? { + didSet { + self.courseNameLabel.text = self.courseTitle + } + } + + var progressText: String? { + didSet { + self.updateProgress() + } + } + + var progress: Float = 0 { + didSet { + self.updateProgress() + } + } + + var coverImageURL: URL? { + didSet { + self.coverImageView.loadImage(url: self.coverImageURL) + } + } + + var tooltipAnchorView: UIView { self.rightDetailImageView } + + override var isHighlighted: Bool { + didSet { + self.subviews.forEach { $0.alpha = self.isHighlighted ? 0.5 : 1.0 } + } + } + + init(frame: CGRect = .zero, appearance: Appearance) { + self.appearance = appearance + super.init(frame: frame) + + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func updateProgress() { + self.statsView.updateProgress( + viewModel: .init(progress: self.progress, progressLabelText: self.progressText ?? "") + ) + } +} + +extension ContinueLastStepView: ProgrammaticallyInitializableViewProtocol { + func addSubviews() { + self.addSubviews([self.coverImageView, self.courseNameLabel, self.statsView, self.rightDetailImageView]) + } + + func makeConstraints() { + self.coverImageView.translatesAutoresizingMaskIntoConstraints = false + self.coverImageView.snp.makeConstraints { make in + make.leading + .equalTo(self.safeAreaLayoutGuide.snp.leading) + .offset(self.appearance.defaultInsets.left) + make.centerY.equalToSuperview() + make.size.equalTo(self.appearance.coverSize) + } + + self.rightDetailImageView.translatesAutoresizingMaskIntoConstraints = false + self.rightDetailImageView.snp.makeConstraints { make in + make.trailing + .equalTo(self.safeAreaLayoutGuide.snp.trailing) + .offset(-self.appearance.defaultInsets.right) + make.centerY.equalTo(self.coverImageView.snp.centerY) + make.size.equalTo(self.appearance.rightDetailImageSize) + } + + self.courseNameLabel.translatesAutoresizingMaskIntoConstraints = false + self.courseNameLabel.snp.makeConstraints { make in + make.top.equalTo(self.coverImageView.snp.top) + make.leading + .equalTo(self.coverImageView.snp.trailing) + .offset(self.appearance.courseLabelInsets.left) + make.trailing + .equalTo(self.rightDetailImageView.snp.leading) + .offset(-self.appearance.courseLabelInsets.right) + } + + self.statsView.translatesAutoresizingMaskIntoConstraints = false + self.statsView.snp.makeConstraints { make in + make.leading.equalTo(self.courseNameLabel.snp.leading) + make.bottom.equalTo(self.coverImageView.snp.bottom) + make.trailing.equalTo(self.courseNameLabel.snp.trailing) + make.height.equalTo(self.appearance.statsViewHeight) + } + } +} diff --git a/Stepic/Sources/Modules/Home/HomeInteractor.swift b/Stepic/Sources/Modules/Home/HomeInteractor.swift index 574b1d279c..66f449131d 100644 --- a/Stepic/Sources/Modules/Home/HomeInteractor.swift +++ b/Stepic/Sources/Modules/Home/HomeInteractor.swift @@ -107,13 +107,4 @@ extension HomeInteractor: StoriesOutputProtocol { } } -extension HomeInteractor: ContinueCourseOutputProtocol { - func hideContinueCourse() { - self.homePresenter?.presentCourseListState( - response: .init( - module: Home.Submodule.continueCourse, - result: .empty - ) - ) - } -} +extension HomeInteractor: ContinueCourseOutputProtocol {} diff --git a/Stepic/Sources/Modules/Home/HomeViewController.swift b/Stepic/Sources/Modules/Home/HomeViewController.swift index bd5c5e320b..76db5f9ede 100644 --- a/Stepic/Sources/Modules/Home/HomeViewController.swift +++ b/Stepic/Sources/Modules/Home/HomeViewController.swift @@ -1,4 +1,5 @@ import PromiseKit +import SnapKit import UIKit protocol HomeViewControllerProtocol: BaseExploreViewControllerProtocol { @@ -10,6 +11,10 @@ protocol HomeViewControllerProtocol: BaseExploreViewControllerProtocol { } final class HomeViewController: BaseExploreViewController { + enum Appearance { + static let continueCourseHeight: CGFloat = 72 + } + enum Animation { static let startRefreshDelay: TimeInterval = 1.0 static let modulesRefreshDelay: TimeInterval = 0.3 @@ -18,7 +23,6 @@ final class HomeViewController: BaseExploreViewController { fileprivate static let submodulesOrder: [Home.Submodule] = [ .stories, .streakActivity, - .continueCourse, .enrolledCourses, .visitedCourses, .popularCourses @@ -155,10 +159,19 @@ final class HomeViewController: BaseExploreViewController { } private func refreshContinueCourse(state: ContinueCourseState) { + var contentInsets = self.exploreView?.contentInsets ?? .zero + if let submodule = self.getSubmodule(type: Home.Submodule.continueCourse) { self.removeSubmodule(submodule) } + defer { + contentInsets.bottom = state == .shown + ? (Appearance.continueCourseHeight + LayoutInsets.default.bottom) + : 0 + self.exploreView?.contentInsets = contentInsets + } + guard case .shown = state else { return } @@ -167,14 +180,22 @@ final class HomeViewController: BaseExploreViewController { output: self.interactor as? ContinueCourseOutputProtocol ) let continueCourseViewController = continueCourseAssembly.makeModule() + self.registerSubmodule( .init( viewController: continueCourseViewController, view: continueCourseViewController.view, + isArrangeable: false, isLanguageDependent: false, type: Home.Submodule.continueCourse ) ) + + continueCourseViewController.view.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview() + make.bottom.equalTo(self.view.safeAreaLayoutGuide) + make.height.equalTo(Appearance.continueCourseHeight) + } } // MARK: - Fullscreen displaying @@ -479,10 +500,7 @@ extension HomeViewController: HomeViewControllerProtocol { func displayModuleErrorState(viewModel: Home.CourseListStateUpdate.ViewModel) { switch viewModel.module { case .continueCourse: - switch viewModel.result { - default: - self.refreshContinueCourse(state: .hidden) - } + self.refreshContinueCourse(state: .hidden) case .enrolledCourses: switch viewModel.result { case .empty: @@ -524,14 +542,13 @@ extension HomeViewController: HomeViewControllerProtocol { let shouldDisplayStories = strongSelf.currentStoriesSubmoduleState == .shown || (strongSelf.currentStoriesSubmoduleState == .hidden && strongSelf.lastContentLanguage != viewModel.contentLanguage) - let shouldDisplayContinueCourse = viewModel.isAuthorized let shouldDisplayAnonymousPlaceholder = !viewModel.isAuthorized strongSelf.lastContentLanguage = viewModel.contentLanguage strongSelf.lastIsAuthorizedFlag = viewModel.isAuthorized strongSelf.refreshStateForStories(state: shouldDisplayStories ? .shown : .hidden) - strongSelf.refreshContinueCourse(state: shouldDisplayContinueCourse ? .shown : .hidden) + strongSelf.refreshContinueCourse(state: .shown) strongSelf.refreshStateForEnrolledCourses(state: shouldDisplayAnonymousPlaceholder ? .anonymous : .normal) strongSelf.refreshStateForVisitedCourses(state: .shown) strongSelf.refreshStateForPopularCourses(state: .normal) diff --git a/Stepic/Sources/Modules/Quizzes/NewFreeAnswerQuiz/NewFreeAnswerQuizView.swift b/Stepic/Sources/Modules/Quizzes/NewFreeAnswerQuiz/NewFreeAnswerQuizView.swift index a2ee6c55a4..88a5108790 100644 --- a/Stepic/Sources/Modules/Quizzes/NewFreeAnswerQuiz/NewFreeAnswerQuizView.swift +++ b/Stepic/Sources/Modules/Quizzes/NewFreeAnswerQuiz/NewFreeAnswerQuizView.swift @@ -39,8 +39,8 @@ final class NewFreeAnswerQuizView: UIView, TitlePresentable { private lazy var textView: TableInputTextView = { let textView = TableInputTextView() textView.textInsets = self.appearance.textFieldInsets - textView.setRoundedCorners( - cornerRadius: self.appearance.textFieldBorderCornerRadius, + textView.roundAllCorners( + radius: self.appearance.textFieldBorderCornerRadius, borderWidth: self.appearance.textFieldBorderWidth, borderColor: self.appearance.textFieldBorderColor ) diff --git a/Stepic/Sources/Modules/Quizzes/NewStringQuiz/NewStringQuizView.swift b/Stepic/Sources/Modules/Quizzes/NewStringQuiz/NewStringQuizView.swift index 834bcdf390..e4c90bcfa9 100644 --- a/Stepic/Sources/Modules/Quizzes/NewStringQuiz/NewStringQuizView.swift +++ b/Stepic/Sources/Modules/Quizzes/NewStringQuiz/NewStringQuizView.swift @@ -53,8 +53,8 @@ final class NewStringQuizView: UIView, TitlePresentable { bottom: self.appearance.textFieldInsets.bottom, right: self.appearance.textFieldDefaultOffset ) - field.setRoundedCorners( - cornerRadius: self.appearance.textFieldBorderCornerRadius, + field.roundAllCorners( + radius: self.appearance.textFieldBorderCornerRadius, borderWidth: self.appearance.textFieldBorderWidth, borderColor: self.appearance.textFieldBorderColor ) diff --git a/Stepic/Sources/Services/ApplicationShortcutService.swift b/Stepic/Sources/Services/ApplicationShortcutService.swift index 7569496ffe..396541e3eb 100644 --- a/Stepic/Sources/Services/ApplicationShortcutService.swift +++ b/Stepic/Sources/Services/ApplicationShortcutService.swift @@ -24,8 +24,8 @@ final class ApplicationShortcutService: ApplicationShortcutServiceProtocol { coursesPersistenceService: CoursesPersistenceServiceProtocol = CoursesPersistenceService(), adaptiveStorageManager: AdaptiveStorageManagerProtocol = AdaptiveStorageManager(), continueCourseProvider: ContinueCourseProviderProtocol = ContinueCourseProvider( - userCoursesAPI: UserCoursesAPI(), - coursesAPI: CoursesAPI(), + userCoursesNetworkService: UserCoursesNetworkService(userCoursesAPI: UserCoursesAPI()), + coursesNetworkService: CoursesNetworkService(coursesAPI: CoursesAPI()), progressesNetworkService: ProgressesNetworkService(progressesAPI: ProgressesAPI()) ), userAccountService: UserAccountServiceProtocol = UserAccountService(), diff --git a/Stepic/Sources/Views/ContinueLastStepView/ContinueActionButton.swift b/Stepic/Sources/Views/ContinueActionButton.swift similarity index 100% rename from Stepic/Sources/Views/ContinueLastStepView/ContinueActionButton.swift rename to Stepic/Sources/Views/ContinueActionButton.swift diff --git a/Stepic/Sources/Views/ContinueLastStepView/ContinueLastStepView.swift b/Stepic/Sources/Views/ContinueLastStepView/ContinueLastStepView.swift deleted file mode 100644 index fb3c0e83a0..0000000000 --- a/Stepic/Sources/Views/ContinueLastStepView/ContinueLastStepView.swift +++ /dev/null @@ -1,248 +0,0 @@ -import SnapKit -import UIKit - -extension ContinueLastStepView { - struct Appearance { - let mainInsets = UIEdgeInsets(top: 20, left: 20, bottom: 0, right: 20) - let contentInsets = UIEdgeInsets(top: 35, left: 19, bottom: 17, right: 19) - let cornerRadius: CGFloat = 8.0 - - let progressHeight: CGFloat = 3.0 - let progressFillColor = UIColor.stepikGreenFixed - let progressBackgroundColor = UIColor.clear - - let lightModeBackgroundOverlayViewColor = UIColor.stepikAccentFixed.withAlphaComponent(0.85) - let darkModeBackgroundOverlayViewColor = UIColor.stepikSecondaryBackground.withAlphaComponent(0.85) - - let coverCornerRadius: CGFloat = 3.0 - - let courseLabelFont = UIFont.systemFont(ofSize: 16, weight: .light) - let progressLabelFont = UIFont.systemFont(ofSize: 12, weight: .light) - let courseLabelTextColor = UIColor.white - let progressLabelTextColor = UIColor.white - - let coverSize = CGSize(width: 36, height: 36) - let infoSpacing: CGFloat = 10.0 - let contentSpacing: CGFloat = 30.0 - - let continueButtonHeight: CGFloat = 42.0 - let continueButtonWidthRatio: CGFloat = 0.65 - } -} - -final class ContinueLastStepView: UIView { - let appearance: Appearance - - lazy var continueButton: UIButton = { - let button = ContinueActionButton(mode: .default) - button.setTitle(NSLocalizedString("ContinueLearningWidgetButtonTitle", comment: ""), for: .normal) - button.addTarget(self, action: #selector(self.continueButtonClicked), for: .touchUpInside) - return button - }() - - // Should use wrapped button cause we have stack view - private lazy var continueButtonBlock = UIView() - - // Contains [continue button] and [info] - private lazy var contentStackView: UIStackView = { - let stackView = UIStackView() - stackView.spacing = self.appearance.contentSpacing - stackView.axis = .vertical - return stackView - }() - - // Contains [course name] and [progress label] - private lazy var labelsStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .vertical - return stackView - }() - - // Contains [cover] and [labels] - private lazy var infoStackView: UIStackView = { - let stackView = UIStackView() - stackView.axis = .horizontal - stackView.spacing = self.appearance.infoSpacing - return stackView - }() - - private lazy var courseNameLabel: UILabel = { - let label = UILabel() - label.textColor = self.appearance.courseLabelTextColor - label.font = self.appearance.courseLabelFont - return label - }() - - private lazy var progressLabel: UILabel = { - let label = UILabel() - label.textColor = self.appearance.progressLabelTextColor - label.font = self.appearance.progressLabelFont - label.lineBreakMode = .byWordWrapping - return label - }() - - private lazy var coverImageView: CourseCoverImageView = { - let view = CourseCoverImageView() - view.clipsToBounds = true - view.layer.cornerRadius = self.appearance.coverCornerRadius - return view - }() - - private lazy var progressView: UIProgressView = { - let view = UIProgressView() - view.progressTintColor = self.appearance.progressFillColor - view.trackTintColor = self.appearance.progressBackgroundColor - view.progress = 0 - return view - }() - - private lazy var overlayView: UIView = { - let view = UIView() - view.backgroundColor = self.appearance.lightModeBackgroundOverlayViewColor - view.clipsToBounds = true - view.layer.cornerRadius = self.appearance.cornerRadius - return view - }() - - private lazy var backgroundImageView: UIImageView = { - let imageView = UIImageView( - image: UIImage(named: "new-coursepics-python-xl"), - highlightedImage: nil - ) - imageView.contentMode = .scaleAspectFill - imageView.clipsToBounds = true - imageView.layer.cornerRadius = self.appearance.cornerRadius - return imageView - }() - - var courseTitle: String? { - didSet { - self.courseNameLabel.text = self.courseTitle - } - } - - var progressText: String? { - didSet { - self.progressLabel.text = self.progressText - } - } - - var progress: Float = 0 { - didSet { - self.progressView.progress = self.progress - } - } - - var coverImageURL: URL? { - didSet { - self.coverImageView.loadImage(url: self.coverImageURL) - } - } - - var onContinueButtonClick: (() -> Void)? - - init(frame: CGRect = .zero, appearance: Appearance = Appearance()) { - self.appearance = appearance - super.init(frame: frame) - - self.setupView() - self.addSubviews() - self.makeConstraints() - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - self.performBlockIfAppearanceChanged(from: previousTraitCollection) { - self.updateViewColor() - } - } - - private func updateViewColor() { - self.progressView.progressTintColor = self.appearance.progressFillColor - self.overlayView.backgroundColor = self.isDarkInterfaceStyle - ? self.appearance.darkModeBackgroundOverlayViewColor - : self.appearance.lightModeBackgroundOverlayViewColor - } - - @objc - private func continueButtonClicked() { - self.onContinueButtonClick?() - } -} - -extension ContinueLastStepView: ProgrammaticallyInitializableViewProtocol { - func setupView() { - self.updateViewColor() - } - - func addSubviews() { - self.labelsStackView.addArrangedSubview(self.courseNameLabel) - self.labelsStackView.addArrangedSubview(self.progressLabel) - - self.infoStackView.addArrangedSubview(self.coverImageView) - self.infoStackView.addArrangedSubview(self.labelsStackView) - - self.continueButtonBlock.addSubview(self.continueButton) - self.contentStackView.addArrangedSubview(self.continueButtonBlock) - self.contentStackView.addArrangedSubview(self.infoStackView) - - self.addSubview(self.backgroundImageView) - self.addSubview(self.overlayView) - - self.overlayView.addSubview(self.contentStackView) - self.overlayView.addSubview(self.progressView) - } - - func makeConstraints() { - self.backgroundImageView.translatesAutoresizingMaskIntoConstraints = false - self.backgroundImageView.snp.makeConstraints { make in - make.center.equalTo(self.overlayView) - make.size.equalTo(self.overlayView) - } - - self.overlayView.translatesAutoresizingMaskIntoConstraints = false - self.overlayView.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(self.appearance.mainInsets.left) - make.trailing.equalToSuperview().offset(-self.appearance.mainInsets.right) - make.top.equalToSuperview().offset(self.appearance.mainInsets.top) - make.bottom.equalToSuperview().offset(-self.appearance.mainInsets.bottom) - } - - self.contentStackView.translatesAutoresizingMaskIntoConstraints = false - self.contentStackView.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(self.appearance.contentInsets.left) - make.trailing.equalToSuperview().offset(-self.appearance.contentInsets.right) - make.top.equalToSuperview().offset(self.appearance.contentInsets.top) - make.bottom.equalToSuperview().offset(-self.appearance.contentInsets.bottom) - } - - self.progressView.translatesAutoresizingMaskIntoConstraints = false - self.progressView.snp.makeConstraints { make in - make.leading.trailing.bottom.equalToSuperview() - make.height.equalTo(self.appearance.progressHeight) - } - - self.coverImageView.translatesAutoresizingMaskIntoConstraints = false - self.coverImageView.snp.makeConstraints { make in - make.size.equalTo(self.appearance.coverSize) - } - - self.continueButtonBlock.translatesAutoresizingMaskIntoConstraints = false - self.continueButtonBlock.snp.makeConstraints { make in - make.height.equalTo(self.appearance.continueButtonHeight) - } - - self.continueButton.translatesAutoresizingMaskIntoConstraints = false - self.continueButton.snp.makeConstraints { make in - make.center.equalToSuperview() - make.top.bottom.equalToSuperview() - make.width.equalTo(self.snp.width).multipliedBy(self.appearance.continueButtonWidthRatio) - } - } -} diff --git a/Stepic/en.lproj/Localizable.strings b/Stepic/en.lproj/Localizable.strings index c4d222794e..bd535b15da 100644 --- a/Stepic/en.lproj/Localizable.strings +++ b/Stepic/en.lproj/Localizable.strings @@ -1011,6 +1011,8 @@ EditStepRemoteUpdateUnsuccessfulTitle = "Failed to update"; CourseWidgetArchived = "Archived"; CourseWidgetSeeAllTitle = "See All"; +ContinueCourseEmptyTitle = "Find your first course"; + /* Course List Filter */ CourseListFilterTitle = "Filters"; CourseListFilterResetButtonTitle = "Reset"; diff --git a/Stepic/ru.lproj/Localizable.strings b/Stepic/ru.lproj/Localizable.strings index 7203b267c2..132753f40f 100644 --- a/Stepic/ru.lproj/Localizable.strings +++ b/Stepic/ru.lproj/Localizable.strings @@ -1012,6 +1012,8 @@ EditStepRemoteUpdateUnsuccessfulTitle = "Не удалось обновить"; CourseWidgetArchived = "В архиве"; CourseWidgetSeeAllTitle = "Посмотреть все"; +ContinueCourseEmptyTitle = "Найдите свой первый курс"; + /* Course List Filter */ CourseListFilterTitle = "Фильтры"; CourseListFilterResetButtonTitle = "Сбросить"; From 600abdfc4f5328b77c4abf9454da6e2474583ace Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 18 Feb 2021 14:59:43 +0300 Subject: [PATCH 4/9] Hide continue course on error --- .../ContinueCourseDataFlow.swift | 1 - .../ContinueCourseInteractor.swift | 3 +-- .../ContinueCourseOutputProtocol.swift | 1 + .../ContinueCoursePresenter.swift | 2 -- .../ContinueCourseViewController.swift | 26 +------------------ .../Views/ContinueCourseView.swift | 8 ------ .../Sources/Modules/Home/HomeInteractor.swift | 11 +++++++- 7 files changed, 13 insertions(+), 39 deletions(-) diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift index f4cb58ab58..1672ab837d 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift @@ -39,7 +39,6 @@ enum ContinueCourse { enum ViewControllerState { case loading case empty - case error case result(data: ContinueCourseViewModel) } } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift index e9e9f0dd71..029c9010a6 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift @@ -47,7 +47,7 @@ final class ContinueCourseInteractor: ContinueCourseInteractorProtocol { } }.catch { _ in if self.currentCourse == nil { - self.presenter.presentLastCourse(response: .init(result: .failure(Error.fetchFailed))) + self.moduleOutput?.hideContinueCourse() } } } @@ -75,7 +75,6 @@ final class ContinueCourseInteractor: ContinueCourseInteractorProtocol { } enum Error: Swift.Error { - case fetchFailed case noLastCourse } } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift index f62f971190..43c6f6cd90 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift @@ -1,5 +1,6 @@ import Foundation protocol ContinueCourseOutputProtocol: AnyObject { + func hideContinueCourse() func presentLastStep(course: Course, isAdaptive: Bool, viewSource: AnalyticsEvent.CourseViewSource) } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCoursePresenter.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCoursePresenter.swift index ef4893f159..2168204609 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCoursePresenter.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCoursePresenter.swift @@ -16,8 +16,6 @@ final class ContinueCoursePresenter: ContinueCoursePresenterProtocol { case .failure(let error): if case ContinueCourseInteractor.Error.noLastCourse = error { self.viewController?.displayLastCourse(viewModel: .init(state: .empty)) - } else { - self.viewController?.displayLastCourse(viewModel: .init(state: .error)) } } } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift index e598a19297..bfbabdc6b3 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift @@ -5,13 +5,10 @@ protocol ContinueCourseViewControllerProtocol: AnyObject { func displayTooltip(viewModel: ContinueCourse.TooltipAvailabilityCheck.ViewModel) } -final class ContinueCourseViewController: UIViewController, ControllerWithStepikPlaceholder { +final class ContinueCourseViewController: UIViewController { private let interactor: ContinueCourseInteractorProtocol private var state: ContinueCourse.ViewControllerState - var placeholderContainer = StepikPlaceholderControllerContainer( - appearance: .init(placeholderAppearance: .init(backgroundColor: .clear)) - ) lazy var continueCourseView = self.view as? ContinueCourseView private lazy var continueLearningTooltip = TooltipFactory.continueLearningWidget @@ -40,22 +37,6 @@ final class ContinueCourseViewController: UIViewController, ControllerWithStepik override func viewDidLoad() { super.viewDidLoad() - - self.registerPlaceholder( - placeholder: StepikPlaceholder( - .tryAgain, - action: { [weak self] in - guard let strongSelf = self else { - return - } - - strongSelf.updateState(newState: .loading) - strongSelf.interactor.doLastCourseRefresh(request: .init()) - } - ), - for: .connectionError - ) - self.updateState(newState: self.state) } @@ -69,19 +50,14 @@ final class ContinueCourseViewController: UIViewController, ControllerWithStepik self.state = newState } - self.isPlaceholderShown = false self.continueCourseView?.hideLoading() self.continueCourseView?.hideEmpty() - self.continueCourseView?.hideError() switch newState { case .loading: self.continueCourseView?.showLoading() case .empty: self.continueCourseView?.showEmpty() - case .error: - self.continueCourseView?.showError() - self.showPlaceholder(for: .connectionError) case .result(let viewModel): self.continueCourseView?.configure(viewModel: viewModel) self.interactor.doTooltipAvailabilityCheck(request: .init()) diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseView.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseView.swift index f7baff58b1..81c7dab729 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseView.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/Views/ContinueCourseView.swift @@ -85,14 +85,6 @@ final class ContinueCourseView: UIView { self.emptyView.isHidden = true } - func showError() { - self.lastStepView.isHidden = true - } - - func hideError() { - self.lastStepView.isHidden = false - } - @objc private func lastStepViewClicked() { self.delegate?.continueCourseDidClickContinue(self) diff --git a/Stepic/Sources/Modules/Home/HomeInteractor.swift b/Stepic/Sources/Modules/Home/HomeInteractor.swift index 66f449131d..547eabee7e 100644 --- a/Stepic/Sources/Modules/Home/HomeInteractor.swift +++ b/Stepic/Sources/Modules/Home/HomeInteractor.swift @@ -107,4 +107,13 @@ extension HomeInteractor: StoriesOutputProtocol { } } -extension HomeInteractor: ContinueCourseOutputProtocol {} +extension HomeInteractor: ContinueCourseOutputProtocol { + func hideContinueCourse() { + self.homePresenter?.presentCourseListState( + response: .init( + module: Home.Submodule.continueCourse, + result: .error + ) + ) + } +} From 36b0996e55ca1e773c74576cedfd318f83447ece Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 18 Feb 2021 15:47:25 +0300 Subject: [PATCH 5/9] Update handle continue course empty action --- .../ContinueCourse/ContinueCourseDataFlow.swift | 5 +++++ .../ContinueCourse/ContinueCourseInteractor.swift | 5 +++++ .../ContinueCourse/ContinueCourseOutputProtocol.swift | 1 + .../ContinueCourse/ContinueCourseViewController.swift | 2 +- Stepic/Sources/Modules/Home/HomeDataFlow.swift | 6 ++++++ Stepic/Sources/Modules/Home/HomeInteractor.swift | 4 ++++ Stepic/Sources/Modules/Home/HomePresenter.swift | 5 +++++ Stepic/Sources/Modules/Home/HomeViewController.swift | 5 +++++ 8 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift index 1672ab837d..f4df375c4f 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseDataFlow.swift @@ -21,6 +21,11 @@ enum ContinueCourse { struct Request {} } + /// Go to catalog + enum ContinueCourseEmptyAction { + struct Request {} + } + /// Check for tooltip enum TooltipAvailabilityCheck { struct Request {} diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift index 029c9010a6..45e0812ed2 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseInteractor.swift @@ -4,6 +4,7 @@ import PromiseKit protocol ContinueCourseInteractorProtocol { func doLastCourseRefresh(request: ContinueCourse.LastCourseLoad.Request) func doContinueLastCourseAction(request: ContinueCourse.ContinueCourseAction.Request) + func doContinueCourseEmptyAction(request: ContinueCourse.ContinueCourseEmptyAction.Request) func doTooltipAvailabilityCheck(request: ContinueCourse.TooltipAvailabilityCheck.Request) } @@ -65,6 +66,10 @@ final class ContinueCourseInteractor: ContinueCourseInteractorProtocol { self.moduleOutput?.presentLastStep(course: currentCourse, isAdaptive: isAdaptive, viewSource: .fastContinue) } + func doContinueCourseEmptyAction(request: ContinueCourse.ContinueCourseEmptyAction.Request) { + self.moduleOutput?.presentCatalog() + } + func doTooltipAvailabilityCheck(request: ContinueCourse.TooltipAvailabilityCheck.Request) { self.presenter.presentTooltip( response: .init( diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift index 43c6f6cd90..536d3512df 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseOutputProtocol.swift @@ -3,4 +3,5 @@ import Foundation protocol ContinueCourseOutputProtocol: AnyObject { func hideContinueCourse() func presentLastStep(course: Course, isAdaptive: Bool, viewSource: AnalyticsEvent.CourseViewSource) + func presentCatalog() } diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift index bfbabdc6b3..303a154915 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContinueCourse/ContinueCourseViewController.swift @@ -97,6 +97,6 @@ extension ContinueCourseViewController: ContinueCourseViewDelegate { } func continueCourseDidClickEmpty(_ continueCourseView: ContinueCourseView) { - DeepLinkRouter.routeToCatalog() + self.interactor.doContinueCourseEmptyAction(request: .init()) } } diff --git a/Stepic/Sources/Modules/Home/HomeDataFlow.swift b/Stepic/Sources/Modules/Home/HomeDataFlow.swift index b90b9e30e4..faa1847f0f 100644 --- a/Stepic/Sources/Modules/Home/HomeDataFlow.swift +++ b/Stepic/Sources/Modules/Home/HomeDataFlow.swift @@ -97,4 +97,10 @@ enum Home { let statusBarStyle: UIStatusBarStyle } } + + enum CatalogPresentation { + struct Response {} + + struct ViewModel {} + } } diff --git a/Stepic/Sources/Modules/Home/HomeInteractor.swift b/Stepic/Sources/Modules/Home/HomeInteractor.swift index 547eabee7e..23fba101fd 100644 --- a/Stepic/Sources/Modules/Home/HomeInteractor.swift +++ b/Stepic/Sources/Modules/Home/HomeInteractor.swift @@ -116,4 +116,8 @@ extension HomeInteractor: ContinueCourseOutputProtocol { ) ) } + + func presentCatalog() { + self.homePresenter?.presentCatalog(response: .init()) + } } diff --git a/Stepic/Sources/Modules/Home/HomePresenter.swift b/Stepic/Sources/Modules/Home/HomePresenter.swift index d504f3ad1a..70f429d34a 100644 --- a/Stepic/Sources/Modules/Home/HomePresenter.swift +++ b/Stepic/Sources/Modules/Home/HomePresenter.swift @@ -6,6 +6,7 @@ protocol HomePresenterProtocol: BaseExplorePresenterProtocol { func presentCourseListState(response: Home.CourseListStateUpdate.Response) func presentStoriesBlock(response: Home.StoriesVisibilityUpdate.Response) func presentStatusBarStyle(response: Home.StatusBarStyleUpdate.Response) + func presentCatalog(response: Home.CatalogPresentation.Response) } final class HomePresenter: BaseExplorePresenter, HomePresenterProtocol { @@ -62,6 +63,10 @@ final class HomePresenter: BaseExplorePresenter, HomePresenterProtocol { self.homeViewController?.displayStatusBarStyle(viewModel: .init(statusBarStyle: response.statusBarStyle)) } + func presentCatalog(response: Home.CatalogPresentation.Response) { + self.homeViewController?.displayCatalog(viewModel: .init()) + } + private func makeStreakActivityMessage(days: Int, needsToSolveToday: Bool) -> String { let pluralizedDaysCnt = StringHelper.pluralize( number: days, diff --git a/Stepic/Sources/Modules/Home/HomeViewController.swift b/Stepic/Sources/Modules/Home/HomeViewController.swift index 76db5f9ede..3c905cc4c8 100644 --- a/Stepic/Sources/Modules/Home/HomeViewController.swift +++ b/Stepic/Sources/Modules/Home/HomeViewController.swift @@ -8,6 +8,7 @@ protocol HomeViewControllerProtocol: BaseExploreViewControllerProtocol { func displayModuleErrorState(viewModel: Home.CourseListStateUpdate.ViewModel) func displayStoriesBlock(viewModel: Home.StoriesVisibilityUpdate.ViewModel) func displayStatusBarStyle(viewModel: Home.StatusBarStyleUpdate.ViewModel) + func displayCatalog(viewModel: Home.CatalogPresentation.ViewModel) } final class HomeViewController: BaseExploreViewController { @@ -564,6 +565,10 @@ extension HomeViewController: HomeViewControllerProtocol { styledNavigationController.changeStatusBarStyle(viewModel.statusBarStyle, sender: self) } } + + func displayCatalog(viewModel: Home.CatalogPresentation.ViewModel) { + DeepLinkRouter.routeToCatalog() + } } extension HomeViewController: BaseExploreViewDelegate { From 5ce05bcf933b73feef491ebf45431af78e4cd780 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 3 Mar 2021 09:24:32 +0300 Subject: [PATCH 6/9] New enrolled courses (#912) * Add gradient background image * Add new placeholders * Update dark mode gradient --- Stepic.xcodeproj/project.pbxproj | 22 ++- .../Contents.json | 25 ++++ .../new_courses_gradient.pdf | Bin 0 -> 6211 bytes .../new_courses_gradient_dark.pdf | Bin 0 -> 4067 bytes .../Contents.json | 15 ++ ...new_courses_placeholder_gradient_large.pdf | Bin 0 -> 6211 bytes .../Contents.json | 15 ++ ...new_courses_placeholder_gradient_small.pdf | Bin 0 -> 6203 bytes .../Views/CourseListColorMode.swift | 19 ++- .../CourseList/Views/CourseListView.swift | 6 +- .../View/CourseListContainerViewFactory.swift | 13 ++ .../ExploreBlockContainerView.swift | 50 +++++-- .../ExploreBlockPlaceholderView.swift | 0 .../ExplorePlaceholderActionButton.swift | 117 ++++++++++++++++ .../ExplorePlaceholderView.swift | 119 ++++++++++++++++ .../NewExploreBlockPlaceholderView.swift | 130 ++++++++++++++++++ .../View/ExploreStoriesContainerView.swift | 4 +- .../Modules/Home/HomeViewController.swift | 30 +++- Stepic/en.lproj/Localizable.strings | 6 + Stepic/ru.lproj/Localizable.strings | 6 + 20 files changed, 544 insertions(+), 33 deletions(-) create mode 100644 Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/Contents.json create mode 100644 Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/new_courses_gradient.pdf create mode 100644 Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/new_courses_gradient_dark.pdf create mode 100644 Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_large.imageset/Contents.json create mode 100644 Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_large.imageset/new_courses_placeholder_gradient_large.pdf create mode 100644 Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_small.imageset/Contents.json create mode 100644 Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_small.imageset/new_courses_placeholder_gradient_small.pdf rename Stepic/Sources/Modules/Explore/View/{ => ExploreBlockPlaceholderView}/ExploreBlockPlaceholderView.swift (100%) create mode 100644 Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExplorePlaceholderActionButton.swift create mode 100644 Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExplorePlaceholderView.swift create mode 100644 Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/NewExploreBlockPlaceholderView.swift diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index ededc0790e..28fd65d7de 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -611,6 +611,9 @@ 2C63C777253EFE2300B20195 /* TableDataset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C63C776253EFE2300B20195 /* TableDataset.swift */; }; 2C64995425DA8586005F63D5 /* PurchaseCourseLocalNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C64995325DA8586005F63D5 /* PurchaseCourseLocalNotificationProvider.swift */; }; 2C68B3C823F5292100171F89 /* ScrollViewKeyboardAdjuster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C68B3C723F5292100171F89 /* ScrollViewKeyboardAdjuster.swift */; }; + 2C6917C625EE47D700BAE0F5 /* NewExploreBlockPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6917C525EE47D700BAE0F5 /* NewExploreBlockPlaceholderView.swift */; }; + 2C6917D525EE489900BAE0F5 /* ExplorePlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6917D425EE489900BAE0F5 /* ExplorePlaceholderView.swift */; }; + 2C6917E325EE650200BAE0F5 /* ExplorePlaceholderActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6917E225EE650200BAE0F5 /* ExplorePlaceholderActionButton.swift */; }; 2C698C9524CEA90100979661 /* NewProfileCoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C698C9424CEA90100979661 /* NewProfileCoverView.swift */; }; 2C6B2F9C20D7F24800F7C976 /* AchievementPopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6B2F9A20D7F24800F7C976 /* AchievementPopupViewController.swift */; }; 2C6B2F9D20D7F24800F7C976 /* AchievementPopupViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C6B2F9B20D7F24800F7C976 /* AchievementPopupViewController.xib */; }; @@ -2247,6 +2250,9 @@ 2C64995325DA8586005F63D5 /* PurchaseCourseLocalNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseCourseLocalNotificationProvider.swift; sourceTree = ""; }; 2C67CA5124E3F9D0002634EB /* Model_profile_personal_preferences_v62.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_profile_personal_preferences_v62.xcdatamodel; sourceTree = ""; }; 2C68B3C723F5292100171F89 /* ScrollViewKeyboardAdjuster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollViewKeyboardAdjuster.swift; sourceTree = ""; }; + 2C6917C525EE47D700BAE0F5 /* NewExploreBlockPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewExploreBlockPlaceholderView.swift; sourceTree = ""; }; + 2C6917D425EE489900BAE0F5 /* ExplorePlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorePlaceholderView.swift; sourceTree = ""; }; + 2C6917E225EE650200BAE0F5 /* ExplorePlaceholderActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplorePlaceholderActionButton.swift; sourceTree = ""; }; 2C698C9424CEA90100979661 /* NewProfileCoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewProfileCoverView.swift; sourceTree = ""; }; 2C6B2F9A20D7F24800F7C976 /* AchievementPopupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementPopupViewController.swift; sourceTree = ""; }; 2C6B2F9B20D7F24800F7C976 /* AchievementPopupViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AchievementPopupViewController.xib; sourceTree = ""; }; @@ -3849,6 +3855,17 @@ path = Views; sourceTree = ""; }; + 2C6917CF25EE484300BAE0F5 /* ExploreBlockPlaceholderView */ = { + isa = PBXGroup; + children = ( + 62E9892AAAD4FAA927DCFC57 /* ExploreBlockPlaceholderView.swift */, + 2C6917E225EE650200BAE0F5 /* ExplorePlaceholderActionButton.swift */, + 2C6917D425EE489900BAE0F5 /* ExplorePlaceholderView.swift */, + 2C6917C525EE47D700BAE0F5 /* NewExploreBlockPlaceholderView.swift */, + ); + path = ExploreBlockPlaceholderView; + sourceTree = ""; + }; 2C6B2F6121AE659600DEB6AC /* Extensions */ = { isa = PBXGroup; children = ( @@ -7926,11 +7943,11 @@ isa = PBXGroup; children = ( 62E984A1DC4A18EBC5E68559 /* CourseListContainerViewFactory.swift */, - 62E9892AAAD4FAA927DCFC57 /* ExploreBlockPlaceholderView.swift */, 62E98101487588BB70A5C1A7 /* ExploreSearchBar.swift */, 62E98664A22248D073D6BAE2 /* ExploreStoriesContainerView.swift */, 2C73B0D725628ECD00EA217D /* ExploreBlockContainerView */, 2C73B0E625628FD300EA217D /* ExploreBlockHeaderView */, + 2C6917CF25EE484300BAE0F5 /* ExploreBlockPlaceholderView */, ); path = View; sourceTree = ""; @@ -9085,6 +9102,7 @@ 2CF9BD3C2538508800C2AFD2 /* PromiseKit+Retry.swift in Sources */, 0813EEA71BFE5A5400DB4B83 /* Assignment.swift in Sources */, 2C06E0B02243CD2D00AF4DA2 /* CourseInfoTabReviewsCellSkeletonView.swift in Sources */, + 2C6917E325EE650200BAE0F5 /* ExplorePlaceholderActionButton.swift in Sources */, 089877A2214047650065DFA2 /* SplitTestingService.swift in Sources */, 2C5D341625C997DF00372C61 /* CurrencySymbolMap.swift in Sources */, 2CF1B3402163BE820008DA0C /* StoriesViewController.swift in Sources */, @@ -9185,6 +9203,7 @@ 2C6FA0B02588B0EC00D50DAA /* DefaultSimpleCourseListView.swift in Sources */, 2C12E4722565668000DC52CB /* UIViewController+PresentPadPanModal.swift in Sources */, 2CA1001421749877003775CB /* NotificationService+PersonalDeadline.swift in Sources */, + 2C6917C625EE47D700BAE0F5 /* NewExploreBlockPlaceholderView.swift in Sources */, 2CA867C12588FFF40006576E /* GridSimpleCourseListCollectionHeaderView.swift in Sources */, 08CBA3011F57459800302154 /* MenuUIManager.swift in Sources */, 2C83E20C2549754400D0C1F3 /* SettingsRightDetailCheckboxTableViewCell.swift in Sources */, @@ -9202,6 +9221,7 @@ 08F485A51C57AF2E000165AA /* FreeAnswerReply.swift in Sources */, 08C1FC331F41E74500E14B46 /* QuizPresenter.swift in Sources */, 2C604E21207E4609001588FB /* CodeEditorPreviewView.swift in Sources */, + 2C6917D525EE489900BAE0F5 /* ExplorePlaceholderView.swift in Sources */, 2CFED65F252C5AC900FCAD41 /* Result+Stepik.swift in Sources */, 2CE42D4423CCC4530073F774 /* StreamVideoQualityStorageManager.swift in Sources */, 083F2B1D1E9D9ABB00714173 /* CertificateViewData.swift in Sources */, diff --git a/Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/Contents.json b/Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/Contents.json new file mode 100644 index 0000000000..36eb76b2c7 --- /dev/null +++ b/Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "new_courses_gradient.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "new_courses_gradient_dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/new_courses_gradient.pdf b/Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/new_courses_gradient.pdf new file mode 100644 index 0000000000000000000000000000000000000000..896ec0ae7aa91e14220f19f88cfc4155743b8247 GIT binary patch literal 6211 zcmeHLOK;ma5We$Q@DiXs#1g5O1rz}`Pb{`Uz4jgo6g?<1DfdoJ2dXuMh zR^-2ZSqtiG;$^W)vYdXP?#NX?%G$w4y=0o?3-RQNvoliFrASufEA0*ukLd%QZ`K`U zQPZzfk=MoA{#0%5I+h4UOKE1zfS)pythTrSeAaGdQnI@@k!wB2T|; zeZPPOm*S5s6*pyKe+K31Gd6d%G@El>lq*e}imKR@si>3$lccUinKxsm{@o^ZnLUE# zPSLohEC@z1594t}jp6PEKBF27-L0M&IkSgkp5+V0|Dq_$d1WF8Zg+!S`;5w|Y@5i*mM3QZV@w*f`)ASc0#fO8=l; z!C_nFsuqBIcQ_hgilrAb zVouYFY$LRLB-`JRak?TnHaM@yutZCfjvJTV8ACMV+$DJ+t?DUA?MxW0(wy9roDBFr z>AV`>$GMNbFF>jhIl>Muh#_U%za?_uwYF7>6x&)5UGR%?^1Eg`A`gZj99WBGEJERA zEJ8LIi(Y#DzF3EX;{at;lwGr(kx`nwL5Jssh>i%U20F(7K6JRFD2iDaH3@?|8oN;( zv1Vqg#4@0rN1#e^U@eyD05`y0UovV^70q=gJVHOch;>zr>Qu)6qaVb2XB1tnmobYV%;SxM=_Ly>S#?Ug^MN+^j#10KxX$JwSASteb1=O5~p3=j;W3Mt~0zefy#@?MAgM2 zAHdOe=vdddGYS!=vbpW59vp*F)XN!E$7mV{M7k`%SVQ;gT7iC71+lJlmfU~J^P+~< zmhLuTBV!?&DCFFVd6pdCod6$dB*RLl+C4!_D2HYjFjb0ZsT0{$tf7~uAFHV_yRQEw zQ@uEKk!c&c;M9%WuBC3?<|J8&3Ml4TU2Q0@->kbx>SS3g^j@`VP>{z`+!L3wh^V?o z{h<&{y?_E;|0JyBkLE4ObAf4(#GweWNGuowE^Jchg0U0^(C6-2eap literal 0 HcmV?d00001 diff --git a/Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/new_courses_gradient_dark.pdf b/Stepic/Images.xcassets/Course_list_placeholder/new_courses_gradient.imageset/new_courses_gradient_dark.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d9ee3797ac968620adcdd0cc42438e07a9a5c31 GIT binary patch literal 4067 zcmc&%O>f&c5WVwP@DiXs#QL@*pa`&W5@XQ@b?rSYu-F45Gmg5p=#{7c&yRPXc_wv{=z5!4 zh|Ws^;qMgGyxFb_vZ=}y*eh94?ODl-?Ca6`3^rWxFL}o2CF?IkS!o;foy4McZbZ4$ zvL&xXU1q#e9xRxYyeyirr+#jkEb~XG>vP|YCTWrco=<%@nRsdF#xaWRvDp%N(@B)L zSiyliodgh!IHj5_x{EzAa#jy)ofjMB!c2&AUD?!f%pXQF_5}N2=lW4X1;7citaynxC)I(>XJRYL)t+S#^(WF}gc;ako&|~&q2G#es<^Ao!IK(} zPQe#lS(lnt9fQ-w1-N9qaxO0grnpHWGYGw~edj{YZ-;%#vVf(t6|tvTMGUDJo9AY9 zyk?fN9Wi6;*l%67_gWZVe4b)&GJPl!&lqyCB2f>-ARf7ptf!qHKRe!Sp0@FAc}tp zp1c9Z0OaB4n9|15U7H>@{TSA=sOmK-5b5sI5aA&tn(#u`py#v7{^w)z!p(budP@s+)CEm108;!tF}sA zW7+m*V6;Yc?Q@fr3%21ENNii(KZve*oiWL_Vxx7H)}SMgCBG*w^#al~=_+5RQ4~ZJ zx+W)@WcQf<-=W3 z3i$UGr_=64S+ab~OM=k-eg(b9n@a5XTlywnzilGnEQNqDAj>&MN=L5iwPmtdt7F NAode-c6R;o<|p4~Zn*#e literal 0 HcmV?d00001 diff --git a/Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_large.imageset/Contents.json b/Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_large.imageset/Contents.json new file mode 100644 index 0000000000..4588bcbe34 --- /dev/null +++ b/Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_large.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "new_courses_placeholder_gradient_large.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_large.imageset/new_courses_placeholder_gradient_large.pdf b/Stepic/Images.xcassets/Course_list_placeholder/new_courses_placeholder_gradient_large.imageset/new_courses_placeholder_gradient_large.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9fb81b865cdd827fbc94ff7c3a896d2e63bc8bb9 GIT binary patch literal 6211 zcmeHLOK;ma5We$Q@DiXs#1bh{vVkJN=845NsMp>@fuaXRW*l{G$(7_qx<&u{4k>Cx z+KL~|2KzvLNXQY%8P0s1*U9+u`jib~LKyXFarcQ(`sNMw-pYJlKTr~jb`_H6H4cvhh)t`uU2fn5t+}7cra4xAxb5U zo>(|k-!(Nrs?wfTk@x=H-6xsWMh!-zp{3EAvVbdg8}z(cu2tUXeG2E5NnWjzQs(KG zt?w7G;8OmPrShgs+|Qs~bH@6vR%Y|Ai*jjb6Im7OGL@BNwkF{1_x-L0M&dDDkvmgRHJ|Dq_$S!E*!3MiAj3WMS*Zcra{-3LBljvJ^`% zro^7671>5;_ei$CA>(vOaBOj2l75L6HXS!Ex-izd{k~X-g5v;X)RbMbosm(Qyg^40M2Lt*aB2=jQWV0y_yOchZ z$Omw=9Xi%E?yN$D>1=Mhst3nl74>Qc)iIjJ1CcHZFjn9Fx>jJ=RY9yPofY?=^1P^_ zwPm_Z*vOd6CJK4Ca+W2>cPGGyTFJ21sc}!x63U_31x&RfTIyIe6>G}c7;Y=ft?Pfu zbT3X{WYUH%ICZVI8>ySOIZo!X0*ZZBR~xG9H|s8vI$0ERvsdrx738s$_r#|xCc3WC ze`q8`ASlqyPr*w4Xx@?nh_IU^4n>HwiisiM!X-sM7|S7X#`Q0Z%>6a=^*{v>r$BG+U-|wvbQmxVRu?RmgZnzEXRMd`uqbe6zNc zMMb|-O{mEdNLoc~ivhGAP&FvAL_1*}Ut#SQ*+>mieYgWT_>X#8oAWtR8dy?>4TA^bss~ zj>bJ7^n1gQg@cgV(Z1zjpBfz47d)s`KAdTuNNbOBHEt8DglZ|G+zbAx`elI|3D{6!mE{Gv*+`lDi;InpBi4@mb5Ls|VJNSLGJy8e44-TBoDh{D= zDh?qVj6*NI|4^Jm!Et~xYRbOZ-l(WdUZbPm4-p*^Qd{U4{QJ*H}HrM9z&!mczlOLhS1`d}3S(AC^O^O%nMQRAY{0e#lT9FV(xM`K^@aNl#PGQ_E^*)grr>n6KPVyL>fN>UjX z_yCTkGskViu2qC^oyqN1^xzm;1-+UXZ2|hW0^+u8R@{Hevb=(} zmgzKM6JsIkDCFJBc^aSInE)T!N`$pejeCN2PzH@IV5$|-P$#OHSW(`@@V3I-n*Ntm z_u=$IrcLOATQ};sk-GjiC-Fj-KxyA)>q7PXdfzCn;$^-tXZ5L0K^_ZvPZ;G9(KU_! zL&G5G2NdY$r(mUiG;2tG228g|0tyg^#Di#u1QB{`m*8TT$baA^~yAkg6h1m$D$r3_p*d@%5_ASMoJ| omCj$+R`sTN4klF=(7P<*UwhiPsI)ymZv%36cJ=Q1FV~DB9smFU literal 0 HcmV?d00001 diff --git a/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift b/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift index 0f27fd4b30..f99a6b1d78 100644 --- a/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift +++ b/Stepic/Sources/Modules/CourseList/Views/CourseListColorMode.swift @@ -4,6 +4,7 @@ enum CourseListColorMode { case light case dark case grouped + case clearLight static var `default`: CourseListColorMode { .light } } @@ -11,7 +12,7 @@ enum CourseListColorMode { extension CourseListColorMode { var exploreBlockHeaderViewAppearance: ExploreBlockHeaderView.Appearance { switch self { - case .light, .grouped: + case .light, .grouped, .clearLight: return .init( titleLabelColor: .stepikSystemPrimaryText, showAllButtonColor: .stepikSystemSecondaryText @@ -26,7 +27,7 @@ extension CourseListColorMode { var exploreBlockContainerViewAppearance: ExploreBlockContainerView.Appearance { var appearance = ExploreBlockContainerView.Appearance() - appearance.backgroundColor = self.exploreBlockContainerViewBackgroundColor + appearance.background = .color(self.exploreBlockContainerViewBackgroundColor) return appearance } @@ -47,6 +48,8 @@ extension CourseListColorMode { return .stepikSecondaryBackground } return .stepikAccentFixed + case .clearLight: + return .clear } } } else { @@ -55,6 +58,8 @@ extension CourseListColorMode { return .white case .dark: return .stepikAccentFixed + case .clearLight: + return .clear } } } @@ -65,7 +70,7 @@ extension CourseListColorMode { var courseWidgetStatsViewAppearance: CourseWidgetStatsView.Appearance { switch self { - case .light, .grouped: + case .light, .grouped, .clearLight: return .init( imagesRenderingBackgroundColor: .stepikSystemSecondaryText, imagesRenderingTintColor: .stepikGreenFixed, @@ -89,7 +94,7 @@ extension CourseListColorMode { ) switch self { - case .light, .grouped: + case .light, .grouped, .clearLight: appearance.textColor = .stepikSystemPrimaryText case .dark: appearance.textColor = .white @@ -105,7 +110,7 @@ extension CourseListColorMode { ) switch self { - case .light, .grouped: + case .light, .grouped, .clearLight: appearance.textColor = .stepikSystemSecondaryText case .dark: appearance.textColor = UIColor.dynamic( @@ -119,7 +124,7 @@ extension CourseListColorMode { var courseWidgetBorderColor: UIColor { switch self { - case .light, .grouped: + case .light, .grouped, .clearLight: return .dynamic(light: .stepikGrey8Fixed, dark: .stepikSeparator) case .dark: if #available(iOS 13.0, *) { @@ -132,7 +137,7 @@ extension CourseListColorMode { var courseWidgetBackgroundColor: UIColor { switch self { - case .light: + case .light, .clearLight: return .dynamic(light: .white, dark: .stepikSecondaryBackground) case .grouped: return .stepikSecondaryGroupedBackground diff --git a/Stepic/Sources/Modules/CourseList/Views/CourseListView.swift b/Stepic/Sources/Modules/CourseList/Views/CourseListView.swift index dc5d284c90..68d085f158 100644 --- a/Stepic/Sources/Modules/CourseList/Views/CourseListView.swift +++ b/Stepic/Sources/Modules/CourseList/Views/CourseListView.swift @@ -148,6 +148,8 @@ class CourseListView: UIView { return self.appearance.darkModeBackgroundColor case .grouped: return self.appearance.groupedModeBackgroundColor + case .clearLight: + return .clear } } @@ -185,7 +187,7 @@ extension CourseListView: ProgrammaticallyInitializableViewProtocol { self.collectionView.translatesAutoresizingMaskIntoConstraints = false switch (self.colorMode, self.cardStyle) { - case (.light, .normal): + case (.light, .normal), (.clearLight, .normal): self.collectionView.register( LightCourseListCollectionViewCell.self, forCellWithReuseIdentifier: LightCourseListCollectionViewCell.defaultReuseIdentifier @@ -200,7 +202,7 @@ extension CourseListView: ProgrammaticallyInitializableViewProtocol { GroupedCourseListCollectionViewCell.self, forCellWithReuseIdentifier: GroupedCourseListCollectionViewCell.defaultReuseIdentifier ) - case (.light, .small): + case (.light, .small), (.clearLight, .small): self.collectionView.register( SmallLightCourseListCollectionViewCell.self, forCellWithReuseIdentifier: SmallLightCourseListCollectionViewCell.defaultReuseIdentifier diff --git a/Stepic/Sources/Modules/Explore/View/CourseListContainerViewFactory.swift b/Stepic/Sources/Modules/Explore/View/CourseListContainerViewFactory.swift index 0aa2a2e191..b3feb9d580 100644 --- a/Stepic/Sources/Modules/Explore/View/CourseListContainerViewFactory.swift +++ b/Stepic/Sources/Modules/Explore/View/CourseListContainerViewFactory.swift @@ -1,6 +1,10 @@ import UIKit final class CourseListContainerViewFactory { + struct HorizontalContainerDescription { + let background: ExploreBlockContainerView.Appearance.Background + } + struct HorizontalHeaderDescription { var title: String? var summary: String? @@ -67,6 +71,7 @@ final class CourseListContainerViewFactory { func makeHorizontalContainerView( for contentView: UIView, + containerDescription: HorizontalContainerDescription? = nil, headerDescription: HorizontalHeaderDescription, headerViewInsets: UIEdgeInsets? = nil, contentViewInsets: UIEdgeInsets? = Appearance.horizontalContentInsets @@ -81,6 +86,7 @@ final class CourseListContainerViewFactory { return self.makeHorizontalContainerView( headerView: headerView, contentView: contentView, + containerDescription: containerDescription, headerViewInsets: headerViewInsets, contentViewInsets: contentViewInsets ) @@ -88,6 +94,7 @@ final class CourseListContainerViewFactory { func makeHorizontalCoursesCollectionContainerView( for contentView: UIView, + containerDescription: HorizontalContainerDescription? = nil, headerDescription: HorizontalCoursesCollectionHeaderDescription, headerViewInsets: UIEdgeInsets? = nil, contentViewInsets: UIEdgeInsets? = Appearance.horizontalContentInsets @@ -102,6 +109,7 @@ final class CourseListContainerViewFactory { return self.makeHorizontalContainerView( headerView: headerView, contentView: contentView, + containerDescription: containerDescription, headerViewInsets: headerViewInsets, contentViewInsets: contentViewInsets ) @@ -130,11 +138,16 @@ final class CourseListContainerViewFactory { private func makeHorizontalContainerView( headerView: UIView & ExploreBlockHeaderViewProtocol, contentView: UIView, + containerDescription: HorizontalContainerDescription?, headerViewInsets: UIEdgeInsets?, contentViewInsets: UIEdgeInsets? ) -> ExploreBlockContainerView { var appearance = self.colorMode.exploreBlockContainerViewAppearance + if let containerDescription = containerDescription { + appearance.background = containerDescription.background + } + if let headerViewInsets = headerViewInsets { appearance.headerViewInsets = headerViewInsets } diff --git a/Stepic/Sources/Modules/Explore/View/ExploreBlockContainerView/ExploreBlockContainerView.swift b/Stepic/Sources/Modules/Explore/View/ExploreBlockContainerView/ExploreBlockContainerView.swift index 71245120ac..39b1084219 100644 --- a/Stepic/Sources/Modules/Explore/View/ExploreBlockContainerView/ExploreBlockContainerView.swift +++ b/Stepic/Sources/Modules/Explore/View/ExploreBlockContainerView/ExploreBlockContainerView.swift @@ -4,11 +4,18 @@ import UIKit extension ExploreBlockContainerView { struct Appearance { let separatorColor = UIColor.stepikSeparator - var backgroundColor = UIColor.stepikBackground + var background = Background.default var headerViewInsets = UIEdgeInsets(top: 20, left: 20, bottom: 0, right: 20) var contentViewInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) let separatorViewInsets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) + + enum Background { + case color(UIColor) + case image(UIImage?) + + static var `default`: Background { .color(.stepikBackground) } + } } } @@ -18,6 +25,18 @@ final class ExploreBlockContainerView: UIView { private let contentView: UIView private let shouldShowSeparator: Bool + private lazy var backgroundImageView: UIImageView = { + let image: UIImage? = { + if case .image(let backgroundImage) = self.appearance.background { + return backgroundImage + } + return nil + }() + let imageView = UIImageView(image: image) + imageView.contentMode = .scaleAspectFill + return imageView + }() + private lazy var separatorView: UIView = { let view = UIView() view.backgroundColor = self.appearance.separatorColor @@ -69,33 +88,36 @@ final class ExploreBlockContainerView: UIView { super.layoutSubviews() self.invalidateIntrinsicContentSize() } - - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - - self.performBlockIfAppearanceChanged(from: previousTraitCollection) { - self.updateViewColor() - } - } - - private func updateViewColor() { - self.backgroundColor = self.appearance.backgroundColor - } } extension ExploreBlockContainerView: ProgrammaticallyInitializableViewProtocol { func setupView() { self.contentView.clipsToBounds = false - self.updateViewColor() + + if case .color(let backgroundColor) = self.appearance.background { + self.backgroundColor = backgroundColor + } } func addSubviews() { + if case .image = self.appearance.background { + self.addSubview(self.backgroundImageView) + } + self.addSubview(self.headerView) self.addSubview(self.contentView) self.addSubview(self.separatorView) } func makeConstraints() { + if case .image = self.appearance.background { + self.backgroundImageView.translatesAutoresizingMaskIntoConstraints = false + self.backgroundImageView.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview() + make.bottom.equalToSuperview().priority(.low) + } + } + self.headerView.translatesAutoresizingMaskIntoConstraints = false self.headerView.snp.makeConstraints { make in make.top.equalToSuperview().offset(self.appearance.headerViewInsets.top) diff --git a/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView.swift b/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExploreBlockPlaceholderView.swift similarity index 100% rename from Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView.swift rename to Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExploreBlockPlaceholderView.swift diff --git a/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExplorePlaceholderActionButton.swift b/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExplorePlaceholderActionButton.swift new file mode 100644 index 0000000000..b90d39329b --- /dev/null +++ b/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExplorePlaceholderActionButton.swift @@ -0,0 +1,117 @@ +import SnapKit +import UIKit + +extension ExplorePlaceholderActionButton { + struct Appearance { + let imageSize = CGSize(width: 20, height: 20) + let insets = LayoutInsets(inset: 12) + + let tintColor = UIColor.stepikVioletFixed + let font = Typography.bodyFont + + let cornerRadius: CGFloat = 8 + let borderWidth: CGFloat = 1 + let borderColor = UIColor(hex6: 0x6C7BDF).withAlphaComponent(0.12) + } +} + +final class ExplorePlaceholderActionButton: UIControl { + let appearance: Appearance + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = self.appearance.font + label.textColor = self.appearance.tintColor + label.textAlignment = .center + label.numberOfLines = 1 + return label + }() + + private lazy var imageView: UIImageView = { + let imageView = UIImageView() + imageView.tintColor = self.appearance.tintColor + imageView.contentMode = .scaleAspectFit + imageView.isHidden = true + return imageView + }() + + var title: String? { + didSet { + self.titleLabel.text = self.title + } + } + + var image: UIImage? { + didSet { + self.imageView.image = self.image + self.imageView.isHidden = self.image == nil + } + } + + override var isHighlighted: Bool { + didSet { + self.titleLabel.alpha = self.isHighlighted ? 0.5 : 1.0 + self.imageView.alpha = self.isHighlighted ? 0.5 : 1.0 + } + } + + override var intrinsicContentSize: CGSize { + let imageWidthWithInsets = self.appearance.insets.left + self.appearance.imageSize.width + let width = imageWidthWithInsets + + self.appearance.insets.left + + self.titleLabel.intrinsicContentSize.width + + self.appearance.insets.right + + let height = max( + self.appearance.imageSize.height, + self.titleLabel.intrinsicContentSize.height + ) + self.appearance.insets.top + self.appearance.insets.bottom + + return CGSize(width: width, height: height) + } + + init( + frame: CGRect = .zero, + appearance: Appearance = Appearance() + ) { + self.appearance = appearance + super.init(frame: frame) + + self.setupView() + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +extension ExplorePlaceholderActionButton: ProgrammaticallyInitializableViewProtocol { + func setupView() { + self.layer.cornerRadius = self.appearance.cornerRadius + self.layer.borderWidth = self.appearance.borderWidth + self.layer.borderColor = self.appearance.borderColor.cgColor + self.clipsToBounds = true + } + + func addSubviews() { + self.addSubview(self.imageView) + self.addSubview(self.titleLabel) + } + + func makeConstraints() { + self.imageView.translatesAutoresizingMaskIntoConstraints = false + self.imageView.snp.makeConstraints { make in + make.leading.equalToSuperview().offset(self.appearance.insets.left) + make.size.equalTo(self.appearance.imageSize) + make.centerY.equalToSuperview() + } + + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.titleLabel.snp.makeConstraints { make in + make.centerY.centerX.equalToSuperview() + } + } +} diff --git a/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExplorePlaceholderView.swift b/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExplorePlaceholderView.swift new file mode 100644 index 0000000000..26f70bb164 --- /dev/null +++ b/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/ExplorePlaceholderView.swift @@ -0,0 +1,119 @@ +import SnapKit +import UIKit + +extension ExplorePlaceholderView { + struct Appearance { + let titleFont: UIFont + let titleTextColor: UIColor + let titleTextAlignment: NSTextAlignment + + let actionButtonHeight: CGFloat = 44 + + let insets = LayoutInsets.default + } +} + +final class ExplorePlaceholderView: UIView { + let appearance: Appearance + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = self.appearance.titleFont + label.textColor = self.appearance.titleTextColor + label.textAlignment = self.appearance.titleTextAlignment + label.numberOfLines = 0 + return label + }() + + private lazy var actionButton: ExplorePlaceholderActionButton = { + let button = ExplorePlaceholderActionButton() + button.addTarget(self, action: #selector(self.actionButtonClicked), for: .touchUpInside) + return button + }() + + private var actionButtonWidthToSuperviewConstraint: Constraint? + + var title: String? { + didSet { + self.titleLabel.text = self.title + } + } + + var buttonTitle: String? { + didSet { + self.actionButton.title = self.buttonTitle + } + } + + var buttonImage: UIImage? { + didSet { + self.actionButton.image = self.buttonImage + + if self.buttonImage != nil { + self.actionButtonWidthToSuperviewConstraint?.activate() + } else { + self.actionButtonWidthToSuperviewConstraint?.deactivate() + } + } + } + + var onActionButtonClick: (() -> Void)? { + didSet { + self.actionButton.isEnabled = self.onActionButtonClick != nil + } + } + + override var intrinsicContentSize: CGSize { + let height = self.titleLabel.intrinsicContentSize.height + + self.appearance.insets.top + + self.appearance.actionButtonHeight + return CGSize(width: UIView.noIntrinsicMetric, height: height) + } + + init(frame: CGRect = .zero, appearance: Appearance) { + self.appearance = appearance + super.init(frame: frame) + + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + self.invalidateIntrinsicContentSize() + } + + @objc + private func actionButtonClicked() { + self.onActionButtonClick?() + } +} + +extension ExplorePlaceholderView: ProgrammaticallyInitializableViewProtocol { + func addSubviews() { + self.addSubview(self.titleLabel) + self.addSubview(self.actionButton) + } + + func makeConstraints() { + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.titleLabel.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview() + } + + self.actionButton.translatesAutoresizingMaskIntoConstraints = false + self.actionButton.snp.makeConstraints { make in + make.top.equalTo(self.titleLabel.snp.bottom).offset(self.appearance.insets.top) + make.bottom.centerX.equalToSuperview() + make.height.equalTo(self.appearance.actionButtonHeight) + + self.actionButtonWidthToSuperviewConstraint = make.width.equalToSuperview().constraint + self.actionButtonWidthToSuperviewConstraint?.deactivate() + } + } +} diff --git a/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/NewExploreBlockPlaceholderView.swift b/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/NewExploreBlockPlaceholderView.swift new file mode 100644 index 0000000000..f48a178282 --- /dev/null +++ b/Stepic/Sources/Modules/Explore/View/ExploreBlockPlaceholderView/NewExploreBlockPlaceholderView.swift @@ -0,0 +1,130 @@ +import SnapKit +import UIKit + +extension NewExploreBlockPlaceholderView { + struct Appearance { + let insets = UIEdgeInsets(top: 20, left: 20, bottom: 12, right: 20) + } +} + +final class NewExploreBlockPlaceholderView: UIView { + let appearance: Appearance + private let placeholderStyle: PlaceholderStyle + + private lazy var placeholderView: ExplorePlaceholderView = { + let view = ExplorePlaceholderView(appearance: self.placeholderStyle.appearance) + view.title = self.placeholderStyle.title + view.buttonTitle = self.placeholderStyle.actionButtonTitle + view.buttonImage = self.placeholderStyle.actionButtonImage + return view + }() + + var onActionButtonClick: (() -> Void)? { + get { + self.placeholderView.onActionButtonClick + } + set { + self.placeholderView.onActionButtonClick = newValue + } + } + + override var intrinsicContentSize: CGSize { + let height = self.appearance.insets.top + + self.placeholderView.intrinsicContentSize.height + + self.appearance.insets.bottom + return CGSize(width: UIView.noIntrinsicMetric, height: height) + } + + init( + frame: CGRect = .zero, + placeholderStyle: PlaceholderStyle, + appearance: Appearance = Appearance() + ) { + self.placeholderStyle = placeholderStyle + self.appearance = appearance + super.init(frame: frame) + + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + self.invalidateIntrinsicContentSize() + } + + enum PlaceholderStyle { + case enrolledEmpty + case error + case anonymous + + fileprivate var title: String { + switch self { + case .enrolledEmpty: + return NSLocalizedString("NewHomePlaceholderEmptyEnrolledTitle", comment: "") + case .error: + return NSLocalizedString("NewHomePlaceholderErrorTitle", comment: "") + case .anonymous: + return NSLocalizedString("NewHomePlaceholderAnonymousTitle", comment: "") + } + } + + fileprivate var actionButtonTitle: String { + switch self { + case .enrolledEmpty: + return NSLocalizedString("NewHomePlaceholderEmptyEnrolledButtonTitle", comment: "") + case .error: + return NSLocalizedString("NewHomePlaceholderErrorButtonTitle", comment: "") + case .anonymous: + return NSLocalizedString("NewHomePlaceholderAnonymousButtonTitle", comment: "") + } + } + + fileprivate var actionButtonImage: UIImage? { + switch self { + case .enrolledEmpty: + return UIImage(named: "plus")?.withRenderingMode(.alwaysTemplate) + case .error, .anonymous: + return nil + } + } + + fileprivate var appearance: ExplorePlaceholderView.Appearance { + switch self { + case .enrolledEmpty: + return .init( + titleFont: Typography.title1Font, + titleTextColor: UIColor.stepikVioletFixed.withAlphaComponent(0.38), + titleTextAlignment: .left + ) + case .error, .anonymous: + return .init( + titleFont: Typography.bodyFont, + titleTextColor: UIColor.stepikMaterialSecondaryText, + titleTextAlignment: .center + ) + } + } + } +} + +extension NewExploreBlockPlaceholderView: ProgrammaticallyInitializableViewProtocol { + func addSubviews() { + self.addSubview(self.placeholderView) + } + + func makeConstraints() { + self.placeholderView.translatesAutoresizingMaskIntoConstraints = false + self.placeholderView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(self.appearance.insets.top) + make.leading.equalToSuperview().offset(self.appearance.insets.left) + make.bottom.equalToSuperview().offset(-self.appearance.insets.bottom) + make.trailing.equalToSuperview().offset(-self.appearance.insets.right) + } + } +} diff --git a/Stepic/Sources/Modules/Explore/View/ExploreStoriesContainerView.swift b/Stepic/Sources/Modules/Explore/View/ExploreStoriesContainerView.swift index 8d5baec07c..85763ec2f6 100644 --- a/Stepic/Sources/Modules/Explore/View/ExploreStoriesContainerView.swift +++ b/Stepic/Sources/Modules/Explore/View/ExploreStoriesContainerView.swift @@ -4,7 +4,7 @@ import UIKit extension ExploreStoriesContainerView { struct Appearance { let storiesViewHeight: CGFloat = 98 - let storiesViewInsets = UIEdgeInsets(top: 16, left: 0, bottom: 0, right: 0) + let storiesViewInsets = UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0) } } @@ -50,7 +50,7 @@ extension ExploreStoriesContainerView: ProgrammaticallyInitializableViewProtocol self.contentView.translatesAutoresizingMaskIntoConstraints = false self.contentView.snp.makeConstraints { make in make.top.equalToSuperview().offset(self.appearance.storiesViewInsets.top) - make.bottom.equalToSuperview() + make.bottom.equalToSuperview().offset(-self.appearance.storiesViewInsets.bottom) make.leading.trailing.equalToSuperview() make.height.equalTo(self.appearance.storiesViewHeight) } diff --git a/Stepic/Sources/Modules/Home/HomeViewController.swift b/Stepic/Sources/Modules/Home/HomeViewController.swift index 3c905cc4c8..0a77d40f88 100644 --- a/Stepic/Sources/Modules/Home/HomeViewController.swift +++ b/Stepic/Sources/Modules/Home/HomeViewController.swift @@ -238,6 +238,17 @@ final class HomeViewController: BaseExploreViewController { case error case empty + var containerDescription: CourseListContainerViewFactory.HorizontalContainerDescription { + switch self { + case .normal: + return .init(background: .image(UIImage(named: "new_courses_gradient"))) + case .empty: + return .init(background: .image(UIImage(named: "new_courses_placeholder_gradient_large"))) + case .anonymous, .error: + return .init(background: .image(UIImage(named: "new_courses_placeholder_gradient_small"))) + } + } + var headerDescription: CourseListContainerViewFactory.HorizontalHeaderDescription { CourseListContainerViewFactory.HorizontalHeaderDescription( title: NSLocalizedString("Enrolled", comment: ""), @@ -246,12 +257,12 @@ final class HomeViewController: BaseExploreViewController { ) } - var message: GradientCoursesPlaceholderViewFactory.InfoPlaceholderMessage { + var placeholderStyle: NewExploreBlockPlaceholderView.PlaceholderStyle { switch self { case .anonymous: - return .login + return .anonymous case .error: - return .enrolledError + return .error case .empty: return .enrolledEmpty default: @@ -264,7 +275,7 @@ final class HomeViewController: BaseExploreViewController { let courseListType = EnrolledCourseListType() let enrolledCourseListAssembly = HorizontalCourseListAssembly( type: courseListType, - colorMode: .light, + colorMode: .clearLight, courseViewSource: .myCourses, output: self.interactor as? CourseListOutputProtocol ) @@ -292,16 +303,20 @@ final class HomeViewController: BaseExploreViewController { (view, viewController) = self.makeEnrolledCourseListSubmodule() } else { // Build placeholder - let placeholderView = ExploreBlockPlaceholderView(message: state.message) + let placeholderView = NewExploreBlockPlaceholderView(placeholderStyle: state.placeholderStyle) switch state { case .anonymous: - placeholderView.onPlaceholderClick = { [weak self] in + placeholderView.onActionButtonClick = { [weak self] in self?.displayAuthorization(viewModel: .init()) } case .error: - placeholderView.onPlaceholderClick = { [weak self] in + placeholderView.onActionButtonClick = { [weak self] in self?.refreshStateForEnrolledCourses(state: .normal) } + case .empty: + placeholderView.onActionButtonClick = { [weak self] in + self?.displayCatalog(viewModel: .init()) + } default: break } @@ -311,6 +326,7 @@ final class HomeViewController: BaseExploreViewController { let containerView = CourseListContainerViewFactory(colorMode: .light) .makeHorizontalContainerView( for: view, + containerDescription: state.containerDescription, headerDescription: state.headerDescription ) diff --git a/Stepic/en.lproj/Localizable.strings b/Stepic/en.lproj/Localizable.strings index 509afbbc98..fad1a158ef 100644 --- a/Stepic/en.lproj/Localizable.strings +++ b/Stepic/en.lproj/Localizable.strings @@ -328,6 +328,12 @@ Popular = "Popular"; VisitedCourses = "Visited courses"; Home = "Home"; RecommendedCategory = "Recommended courses"; +NewHomePlaceholderAnonymousTitle = "Sign in and start learning right now"; +NewHomePlaceholderAnonymousButtonTitle = "Sign In"; +NewHomePlaceholderErrorTitle = "Connection is lost"; +NewHomePlaceholderErrorButtonTitle = "Try Again"; +NewHomePlaceholderEmptyEnrolledTitle = "Your courses will be here"; +NewHomePlaceholderEmptyEnrolledButtonTitle = "Find Courses in Catalog"; HomePlaceholderAnonymous = "Sign in and start learning right now"; HomePlaceholderEmptyEnrolled = "Enroll for free courses and they will be here"; HomePlaceholderEmptyPopular = "Open courses are on vacation. Please, see later"; diff --git a/Stepic/ru.lproj/Localizable.strings b/Stepic/ru.lproj/Localizable.strings index 4e9df3f30c..c07f560896 100644 --- a/Stepic/ru.lproj/Localizable.strings +++ b/Stepic/ru.lproj/Localizable.strings @@ -329,6 +329,12 @@ Popular = "Популярные"; VisitedCourses = "Посещённые курсы"; Home = "Обучение"; RecommendedCategory = "Подборка"; +NewHomePlaceholderAnonymousTitle = "Войдите и начните учиться прямо сейчас"; +NewHomePlaceholderAnonymousButtonTitle = "Войти"; +NewHomePlaceholderErrorTitle = "Соединение потеряно"; +NewHomePlaceholderErrorButtonTitle = "Повторить"; +NewHomePlaceholderEmptyEnrolledTitle = "Здесь появятся ваши Курсы"; +NewHomePlaceholderEmptyEnrolledButtonTitle = "Найти курсы в Каталоге"; HomePlaceholderAnonymous = "Войдите и начните учиться прямо сейчас"; HomePlaceholderEmptyEnrolled = "Запишитесь на курсы и они будут показаны здесь"; HomePlaceholderEmptyPopular = "Открытые курсы сейчас в отпуске. Пожалуйста, зайдите позже"; From 8ffad075d2fe68b83bddb11e61543cf4cee089ac Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Wed, 3 Mar 2021 15:38:24 +0300 Subject: [PATCH 7/9] Bump build --- Stepic.xcodeproj/project.pbxproj | 24 +++++++++++----------- Stepic/Info-Develop.plist | 2 +- Stepic/Info-Production.plist | 2 +- Stepic/Info-Release.plist | 2 +- StepicTests/Info-Develop.plist | 2 +- StepicTests/Info-Production.plist | 2 +- StepicTests/Info-Release.plist | 2 +- StepicWidget/Info-Develop.plist | 2 +- StepicWidget/Info-Production.plist | 2 +- StepicWidget/Info-Release.plist | 2 +- StickerPackExtension/Info-Develop.plist | 2 +- StickerPackExtension/Info-Production.plist | 2 +- StickerPackExtension/Info-Release.plist | 2 +- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index 28fd65d7de..fd7fb44ead 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -10537,7 +10537,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Production.plist"; @@ -10562,7 +10562,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Production.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; @@ -10704,7 +10704,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Production.plist"; @@ -10734,7 +10734,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; @@ -10825,7 +10825,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Develop.plist"; @@ -10877,7 +10877,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Develop.plist"; @@ -10958,7 +10958,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Develop.plist"; @@ -11006,7 +11006,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Develop.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; @@ -11527,7 +11527,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Release.plist"; @@ -11581,7 +11581,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Release.plist"; @@ -11663,7 +11663,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Release.plist"; @@ -11711,7 +11711,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 296; + CURRENT_PROJECT_VERSION = 297; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Release.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; diff --git a/Stepic/Info-Develop.plist b/Stepic/Info-Develop.plist index 67aa0fe262..a8a815e534 100644 --- a/Stepic/Info-Develop.plist +++ b/Stepic/Info-Develop.plist @@ -62,7 +62,7 @@ CFBundleVersion - 296 + 297 FacebookAppID 171127739724012 FacebookDisplayName diff --git a/Stepic/Info-Production.plist b/Stepic/Info-Production.plist index 9c5d1744a6..9853fd00f5 100644 --- a/Stepic/Info-Production.plist +++ b/Stepic/Info-Production.plist @@ -62,7 +62,7 @@ CFBundleVersion - 296 + 297 FacebookAppID 171127739724012 FacebookDisplayName diff --git a/Stepic/Info-Release.plist b/Stepic/Info-Release.plist index 8622cf0a05..54ec403802 100644 --- a/Stepic/Info-Release.plist +++ b/Stepic/Info-Release.plist @@ -62,7 +62,7 @@ CFBundleVersion - 296 + 297 FacebookAppID 171127739724012 FacebookDisplayName diff --git a/StepicTests/Info-Develop.plist b/StepicTests/Info-Develop.plist index 6f24f2042b..6ed859fed2 100644 --- a/StepicTests/Info-Develop.plist +++ b/StepicTests/Info-Develop.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 296 + 297 diff --git a/StepicTests/Info-Production.plist b/StepicTests/Info-Production.plist index d1baa4334b..78de684d99 100644 --- a/StepicTests/Info-Production.plist +++ b/StepicTests/Info-Production.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 296 + 297 diff --git a/StepicTests/Info-Release.plist b/StepicTests/Info-Release.plist index 979637186b..635c808165 100644 --- a/StepicTests/Info-Release.plist +++ b/StepicTests/Info-Release.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 296 + 297 diff --git a/StepicWidget/Info-Develop.plist b/StepicWidget/Info-Develop.plist index 37eda79e6d..ee9c6ddc92 100644 --- a/StepicWidget/Info-Develop.plist +++ b/StepicWidget/Info-Develop.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.162-develop CFBundleVersion - 296 + 297 NSExtension NSExtensionPointIdentifier diff --git a/StepicWidget/Info-Production.plist b/StepicWidget/Info-Production.plist index 575ffbe213..3231d0e0f3 100644 --- a/StepicWidget/Info-Production.plist +++ b/StepicWidget/Info-Production.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.162 CFBundleVersion - 296 + 297 NSExtension NSExtensionPointIdentifier diff --git a/StepicWidget/Info-Release.plist b/StepicWidget/Info-Release.plist index 3eef45aad3..cbd75aa8b1 100644 --- a/StepicWidget/Info-Release.plist +++ b/StepicWidget/Info-Release.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.162-release CFBundleVersion - 296 + 297 NSExtension NSExtensionPointIdentifier diff --git a/StickerPackExtension/Info-Develop.plist b/StickerPackExtension/Info-Develop.plist index f161d3eb89..b7df0ec10f 100644 --- a/StickerPackExtension/Info-Develop.plist +++ b/StickerPackExtension/Info-Develop.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.162-develop CFBundleVersion - 296 + 297 NSExtension NSExtensionPointIdentifier diff --git a/StickerPackExtension/Info-Production.plist b/StickerPackExtension/Info-Production.plist index ac06d35e0f..8a9f6cc017 100644 --- a/StickerPackExtension/Info-Production.plist +++ b/StickerPackExtension/Info-Production.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.162 CFBundleVersion - 296 + 297 NSExtension NSExtensionPointIdentifier diff --git a/StickerPackExtension/Info-Release.plist b/StickerPackExtension/Info-Release.plist index 958923f09f..70f3437ea9 100644 --- a/StickerPackExtension/Info-Release.plist +++ b/StickerPackExtension/Info-Release.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.162-release CFBundleVersion - 296 + 297 NSExtension NSExtensionPointIdentifier From 9d67c52be6f488475289a8f1fba31cea5b48d7ce Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 18 Mar 2021 16:55:11 +0300 Subject: [PATCH 8/9] Apply fixes after merge --- .../FeedbackStoryView/FeedbackStoryFormView.swift | 4 ++-- .../StoryPartViews/FeedbackStoryView/FeedbackStoryView.swift | 2 +- .../Modules/BaseExplore/BaseExploreViewController.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Stepic/Legacy/Controllers/Stories/StoryPartViews/FeedbackStoryView/FeedbackStoryFormView.swift b/Stepic/Legacy/Controllers/Stories/StoryPartViews/FeedbackStoryView/FeedbackStoryFormView.swift index 1c9a037435..e9fd930a4a 100644 --- a/Stepic/Legacy/Controllers/Stories/StoryPartViews/FeedbackStoryView/FeedbackStoryFormView.swift +++ b/Stepic/Legacy/Controllers/Stories/StoryPartViews/FeedbackStoryView/FeedbackStoryFormView.swift @@ -45,7 +45,7 @@ final class FeedbackStoryFormView: UIView { textView.isScrollEnabled = true textView.isUserInteractionEnabled = true textView.dataDetectorTypes = [] - textView.setRoundedCorners(cornerRadius: self.appearance.inputTextViewCornerRadius) + textView.roundAllCorners(radius: self.appearance.inputTextViewCornerRadius) return textView }() @@ -144,7 +144,7 @@ final class FeedbackStoryFormView: UIView { extension FeedbackStoryFormView: ProgrammaticallyInitializableViewProtocol { func setupView() { - self.setRoundedCorners(cornerRadius: self.appearance.cornerRadius) + self.roundAllCorners(radius: self.appearance.cornerRadius) } func addSubviews() { diff --git a/Stepic/Legacy/Controllers/Stories/StoryPartViews/FeedbackStoryView/FeedbackStoryView.swift b/Stepic/Legacy/Controllers/Stories/StoryPartViews/FeedbackStoryView/FeedbackStoryView.swift index 94b836081c..4c2ef9b93d 100644 --- a/Stepic/Legacy/Controllers/Stories/StoryPartViews/FeedbackStoryView/FeedbackStoryView.swift +++ b/Stepic/Legacy/Controllers/Stories/StoryPartViews/FeedbackStoryView/FeedbackStoryView.swift @@ -43,7 +43,7 @@ final class FeedbackStoryView: UIView, UIStoryPartViewProtocol { button.addTarget(self, action: #selector(self.actionButtonClicked), for: .touchUpInside) let cornerRadius = self.appearance.actionButtonHeight / 2 - button.setRoundedCorners(cornerRadius: cornerRadius) + button.roundAllCorners(radius: cornerRadius) button.titleInsets = .init(top: 0, left: cornerRadius, bottom: 0, right: cornerRadius) diff --git a/Stepic/Sources/Modules/BaseExplore/BaseExploreViewController.swift b/Stepic/Sources/Modules/BaseExplore/BaseExploreViewController.swift index 74de923113..8b8e4fc42a 100644 --- a/Stepic/Sources/Modules/BaseExplore/BaseExploreViewController.swift +++ b/Stepic/Sources/Modules/BaseExplore/BaseExploreViewController.swift @@ -134,7 +134,7 @@ class BaseExploreViewController: UIViewController { struct Submodule { let viewController: UIViewController? let view: UIView - var isArrangeable: Bool = true + var isArrangeable = true let isLanguageDependent: Bool let type: SubmoduleType } From dd15962a90653529b1f6f4c5757bd79ec1fa2b38 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 23 Dec 2021 13:32:39 +0300 Subject: [PATCH 9/9] Bump build --- Stepic.xcodeproj/project.pbxproj | 12 ++--- Stepic/Info-Production.plist | 92 ++++++++++++++++---------------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index aedb3581b5..b1bb4709bc 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -13174,7 +13174,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 398; + CURRENT_PROJECT_VERSION = 399; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Production.plist"; @@ -13204,7 +13204,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 398; + CURRENT_PROJECT_VERSION = 399; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; @@ -13295,7 +13295,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 398; + CURRENT_PROJECT_VERSION = 399; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Develop.plist"; @@ -13428,7 +13428,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 398; + CURRENT_PROJECT_VERSION = 399; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Develop.plist"; @@ -13996,7 +13996,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 398; + CURRENT_PROJECT_VERSION = 399; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Release.plist"; @@ -14132,7 +14132,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 398; + CURRENT_PROJECT_VERSION = 399; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Release.plist"; diff --git a/Stepic/Info-Production.plist b/Stepic/Info-Production.plist index 0f7d8ff08e..92175e85e2 100644 --- a/Stepic/Info-Production.plist +++ b/Stepic/Info-Production.plist @@ -62,7 +62,7 @@ CFBundleVersion - 398 + $(CURRENT_PROJECT_VERSION) FacebookAppID 171127739724012 FacebookDisplayName @@ -85,52 +85,19 @@ NSAllowsArbitraryLoads - UIBackgroundModes - - audio - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UIStatusBarStyle - UIStatusBarStyleLightContent - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad + NSUserActivityTypes - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight + $(PRODUCT_BUNDLE_IDENTIFIER).ContinueLearningUserActivity - UIViewControllerBasedStatusBarAppearance - - branch_key - - live - key_live_ekt7qHLldFSKQyO2DT3NYellwFfko55Q - test - key_test_gjtWCOSjnAMKMtV9qJ4YyeelFzced092 - UIApplicationShortcutItems - UIApplicationShortcutItemType - $(PRODUCT_BUNDLE_IDENTIFIER).ContinueLearning UIApplicationShortcutItemIconType UIApplicationShortcutIconTypePlay UIApplicationShortcutItemTitle shortcutTitleContinueLearning + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).ContinueLearning UIApplicationShortcutItemUserInfo version @@ -138,12 +105,12 @@ - UIApplicationShortcutItemType - $(PRODUCT_BUNDLE_IDENTIFIER).SearchCourses UIApplicationShortcutItemIconType UIApplicationShortcutIconTypeSearch UIApplicationShortcutItemTitle shortcutTitleSearchCourses + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).SearchCourses UIApplicationShortcutItemUserInfo version @@ -151,12 +118,12 @@ - UIApplicationShortcutItemType - $(PRODUCT_BUNDLE_IDENTIFIER).Profile UIApplicationShortcutItemIconType UIApplicationShortcutIconTypeContact UIApplicationShortcutItemTitle shortcutTitleProfile + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).Profile UIApplicationShortcutItemUserInfo version @@ -164,12 +131,12 @@ - UIApplicationShortcutItemType - $(PRODUCT_BUNDLE_IDENTIFIER).Notifications UIApplicationShortcutItemIconFile shortcut-item-notifications UIApplicationShortcutItemTitle shortcutTitleNotifications + UIApplicationShortcutItemType + $(PRODUCT_BUNDLE_IDENTIFIER).Notifications UIApplicationShortcutItemUserInfo version @@ -177,9 +144,42 @@ - NSUserActivityTypes + UIBackgroundModes - $(PRODUCT_BUNDLE_IDENTIFIER).ContinueLearningUserActivity + audio + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + branch_key + + live + key_live_ekt7qHLldFSKQyO2DT3NYellwFfko55Q + test + key_test_gjtWCOSjnAMKMtV9qJ4YyeelFzced092 +