From 14262001badfb856c5ff56faf4f2727de3b43c84 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 24 Jul 2018 13:21:55 +0300 Subject: [PATCH 01/14] Update launch screen Add Stepik logo. --- .../logo.imageset/Contents.json | 21 +++++++++ .../Assets.xcassets/logo.imageset/logo.png | Bin 0 -> 19351 bytes .../Base.lproj/LaunchScreen.storyboard | 29 ------------ .../PresentationLayer/LaunchScreen.storyboard | 43 ++++++++++++++++++ Stepic.xcodeproj/project.pbxproj | 20 ++++---- 5 files changed, 72 insertions(+), 41 deletions(-) create mode 100644 ExamEGERussian/Resources/Assets/Assets.xcassets/logo.imageset/Contents.json create mode 100644 ExamEGERussian/Resources/Assets/Assets.xcassets/logo.imageset/logo.png delete mode 100644 ExamEGERussian/Sources/PresentationLayer/Base.lproj/LaunchScreen.storyboard create mode 100644 ExamEGERussian/Sources/PresentationLayer/LaunchScreen.storyboard diff --git a/ExamEGERussian/Resources/Assets/Assets.xcassets/logo.imageset/Contents.json b/ExamEGERussian/Resources/Assets/Assets.xcassets/logo.imageset/Contents.json new file mode 100644 index 0000000000..8463acafa9 --- /dev/null +++ b/ExamEGERussian/Resources/Assets/Assets.xcassets/logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "logo.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ExamEGERussian/Resources/Assets/Assets.xcassets/logo.imageset/logo.png b/ExamEGERussian/Resources/Assets/Assets.xcassets/logo.imageset/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fe9ef3cebc6f355616141ffe20855b38be32bcc4 GIT binary patch literal 19351 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 TuTRVnHjflkS literal 0 HcmV?d00001 diff --git a/ExamEGERussian/Sources/PresentationLayer/Base.lproj/LaunchScreen.storyboard b/ExamEGERussian/Sources/PresentationLayer/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index d26d12be61..0000000000 --- a/ExamEGERussian/Sources/PresentationLayer/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ExamEGERussian/Sources/PresentationLayer/LaunchScreen.storyboard b/ExamEGERussian/Sources/PresentationLayer/LaunchScreen.storyboard new file mode 100644 index 0000000000..d10f9d2e88 --- /dev/null +++ b/ExamEGERussian/Sources/PresentationLayer/LaunchScreen.storyboard @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index 82d5fd94f4..8b9692cda6 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -1881,6 +1881,7 @@ 2C1B64E01F4C596500236804 /* Config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C1B64CE1F4C595F00236804 /* Config.plist */; }; 2C1B64E31F4C597F00236804 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 2C1B64D31F4C595F00236804 /* icon.png */; }; 2C1B64E81F4C5A9E00236804 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C1B64E61F4C5A6D00236804 /* Icons.xcassets */; }; + 2C1BF0B02107328900EA83CA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C1BF0AF2107328900EA83CA /* LaunchScreen.storyboard */; }; 2C22042520E277B40060117A /* Skeletonable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22042420E277B40060117A /* Skeletonable.swift */; }; 2C22042720E277E50060117A /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22042620E277E50060117A /* SkeletonView.swift */; }; 2C22042B20E277F70060117A /* SkeletonViewAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22042820E277F60060117A /* SkeletonViewAnimation.swift */; }; @@ -2018,7 +2019,6 @@ 2C4BBF26203DC670000A4250 /* plyr.js in Resources */ = {isa = PBXBuildFile; fileRef = 2C4BBF10203DC668000A4250 /* plyr.js */; }; 2C4DA9E91F38729500E392FA /* LocalNotificationsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4DA9E81F38729500E392FA /* LocalNotificationsHelper.swift */; }; 2C59F03120EB8AC900666EE0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C59F03020EB8AC900666EE0 /* Assets.xcassets */; }; - 2C59F03420EB8AC900666EE0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C59F03220EB8AC900666EE0 /* LaunchScreen.storyboard */; }; 2C59F03F20EB8ACA00666EE0 /* TopicsTableViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C59F03E20EB8ACA00666EE0 /* TopicsTableViewControllerTests.swift */; }; 2C59F04A20EB8ACA00666EE0 /* ExamEGERussianUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C59F04920EB8ACA00666EE0 /* ExamEGERussianUITests.swift */; }; 2C5D51592024653B00B9D932 /* BaseCardsStepsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5D51582024653B00B9D932 /* BaseCardsStepsViewController.swift */; }; @@ -5564,6 +5564,7 @@ 2C1B64D11F4C595F00236804 /* AdaptiveInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AdaptiveInfo.plist; path = Content/3124/AdaptiveInfo.plist; sourceTree = ""; }; 2C1B64D31F4C595F00236804 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon.png; path = Content/3124/icon.png; sourceTree = ""; }; 2C1B64E61F4C5A6D00236804 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Icons.xcassets; path = Content/3124/Icons.xcassets; sourceTree = ""; }; + 2C1BF0AF2107328900EA83CA /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 2C22042420E277B40060117A /* Skeletonable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Skeletonable.swift; sourceTree = ""; }; 2C22042620E277E50060117A /* SkeletonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkeletonView.swift; sourceTree = ""; }; 2C22042820E277F60060117A /* SkeletonViewAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkeletonViewAnimation.swift; sourceTree = ""; }; @@ -5610,7 +5611,6 @@ 2C59F02720EB8AC800666EE0 /* ExamEGERussian.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExamEGERussian.app; sourceTree = BUILT_PRODUCTS_DIR; }; 2C59F02920EB8AC800666EE0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 2C59F03020EB8AC900666EE0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 2C59F03320EB8AC900666EE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 2C59F03520EB8AC900666EE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2C59F03A20EB8ACA00666EE0 /* ExamEGERussianTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExamEGERussianTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 2C59F03E20EB8ACA00666EE0 /* TopicsTableViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicsTableViewControllerTests.swift; sourceTree = ""; }; @@ -8497,7 +8497,7 @@ isa = PBXGroup; children = ( 2C59F05820EB8E3700666EE0 /* UserStories */, - 2C59F03220EB8AC900666EE0 /* LaunchScreen.storyboard */, + 2C1BF0AF2107328900EA83CA /* LaunchScreen.storyboard */, ); path = PresentationLayer; sourceTree = ""; @@ -10854,9 +10854,9 @@ 2CFE248120ED112100AF1E3D /* Auth.plist in Resources */, 2CFE247C20ECF7F700AF1E3D /* Config.plist in Resources */, 2CE3C2BA21007D3D003AB54A /* TopicsTableViewController.xib in Resources */, - 2C59F03420EB8AC900666EE0 /* LaunchScreen.storyboard in Resources */, 2C59F03120EB8AC900666EE0 /* Assets.xcassets in Resources */, 2CCB5A3E2100824F0091A605 /* TopicTableViewCell.xib in Resources */, + 2C1BF0B02107328900EA83CA /* LaunchScreen.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -12850,8 +12850,10 @@ "${BUILT_PRODUCTS_DIR}/lottie-ios/Lottie.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/pop/pop.framework", + "${BUILT_PRODUCTS_DIR}/Mockingjay/Mockingjay.framework", "${BUILT_PRODUCTS_DIR}/Nimble-iOS/Nimble.framework", "${BUILT_PRODUCTS_DIR}/Quick-iOS/Quick.framework", + "${BUILT_PRODUCTS_DIR}/URITemplate/URITemplate.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -12895,8 +12897,10 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Lottie.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pop.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mockingjay.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/URITemplate.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -17296,14 +17300,6 @@ name = step0.html; sourceTree = ""; }; - 2C59F03220EB8AC900666EE0 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 2C59F03320EB8AC900666EE0 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; 2C6640E41F30BCE60033A274 /* AchievementTableViewCell.xib */ = { isa = PBXVariantGroup; children = ( From e09c6f043e82e8b93a78e6f6471a1aaff29f5d07 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 24 Jul 2018 14:21:36 +0300 Subject: [PATCH 02/14] Initial commit for lessons scene --- .../RootNavigationManager.swift | 17 +++- .../LessonTableViewCell.swift | 13 +++ .../LessonTableViewCell.xib | 40 ++++++++ .../LessonsTableViewController.swift | 95 +++++++++++++++++++ .../LessonsTableViewController.xib | 26 +++++ .../Topics/Presenter/TopicsPresenter.swift | 0 .../Presenter/TopicsPresenterImpl.swift | 14 +-- .../TopicTableViewCell.swift | 0 .../TopicTableViewCell/TopicTableViewCell.xib | 0 .../TopicsTableViewController.swift | 0 .../TopicsTableViewController.xib | 0 .../Topics/View/TopicsView.swift | 0 Stepic.xcodeproj/project.pbxproj | 58 +++++++++-- 13 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.swift create mode 100644 ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.xib create mode 100644 ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift create mode 100644 ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.xib rename ExamEGERussian/Sources/PresentationLayer/{UserStories => }/Topics/Presenter/TopicsPresenter.swift (100%) rename ExamEGERussian/Sources/PresentationLayer/{UserStories => }/Topics/Presenter/TopicsPresenterImpl.swift (89%) rename ExamEGERussian/Sources/PresentationLayer/{UserStories => }/Topics/View/TopicTableViewCell/TopicTableViewCell.swift (100%) rename ExamEGERussian/Sources/PresentationLayer/{UserStories => }/Topics/View/TopicTableViewCell/TopicTableViewCell.xib (100%) rename ExamEGERussian/Sources/PresentationLayer/{UserStories => }/Topics/View/TopicsTableViewController/TopicsTableViewController.swift (100%) rename ExamEGERussian/Sources/PresentationLayer/{UserStories => }/Topics/View/TopicsTableViewController/TopicsTableViewController.xib (100%) rename ExamEGERussian/Sources/PresentationLayer/{UserStories => }/Topics/View/TopicsView.swift (100%) diff --git a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift index 7b4c4097c3..80c6453617 100644 --- a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift +++ b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift @@ -8,13 +8,12 @@ import UIKit -// MARK: RootNavigationManager - final class RootNavigationManager { - // MARK: - Instance Variables + // MARK: - Instance Properties private unowned let serviceComponents: ServiceComponents + private weak var navigationController: UINavigationController? // MARK: Init @@ -29,13 +28,23 @@ final class RootNavigationManager { controller.presenter = TopicsPresenterImpl( view: controller, model: KnowledgeGraph(), + router: self, userRegistrationService: serviceComponents.userRegistrationService, graphService: serviceComponents.graphService ) - let navigationController = UINavigationController(rootViewController: controller) + navigationController = UINavigationController(rootViewController: controller) window.rootViewController = navigationController window.makeKeyAndVisible() } } + +// MARK: - RootNavigationManager: TopicsRouter - + +extension RootNavigationManager: TopicsRouter { + func showLessonsForTopicWithId(_ id: String) { + let controller = LessonsTableViewController() + navigationController?.pushViewController(controller, animated: true) + } +} diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.swift new file mode 100644 index 0000000000..829d32dcaa --- /dev/null +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.swift @@ -0,0 +1,13 @@ +// +// LessonTableViewCell.swift +// ExamEGERussian +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +final class LessonTableViewCell: UITableViewCell { + @IBOutlet var descriptionTitleLabel: UILabel! +} diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.xib b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.xib new file mode 100644 index 0000000000..a8aec1d898 --- /dev/null +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.xib @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift new file mode 100644 index 0000000000..65efc41768 --- /dev/null +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift @@ -0,0 +1,95 @@ +// +// LessonsTableViewController.swift +// ExamEGERussian +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +class LessonsTableViewController: UITableViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Uncomment the following line to preserve selection between presentations + // self.clearsSelectionOnViewWillAppear = false + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + // #warning Incomplete implementation, return the number of sections + return 0 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // #warning Incomplete implementation, return the number of rows + return 0 + } + + /* + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) + + // Configure the cell... + + return cell + } + */ + + /* + // Override to support conditional editing of the table view. + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the specified item to be editable. + return true + } + */ + + /* + // Override to support editing the table view. + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + // Delete the row from the data source + tableView.deleteRows(at: [indexPath], with: .fade) + } else if editingStyle == .insert { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } + } + */ + + /* + // Override to support rearranging the table view. + override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { + + } + */ + + /* + // Override to support conditional rearranging of the table view. + override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the item to be re-orderable. + return true + } + */ + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destinationViewController. + // Pass the selected object to the new view controller. + } + */ + +} diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.xib b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.xib new file mode 100644 index 0000000000..de53f77ed1 --- /dev/null +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.xib @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/Presenter/TopicsPresenter.swift b/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenter.swift similarity index 100% rename from ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/Presenter/TopicsPresenter.swift rename to ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenter.swift diff --git a/ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/Presenter/TopicsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift similarity index 89% rename from ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/Presenter/TopicsPresenterImpl.swift rename to ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift index 047d86fb4d..fcd939932b 100644 --- a/ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/Presenter/TopicsPresenterImpl.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift @@ -8,17 +8,23 @@ import Foundation +protocol TopicsRouter: class { + func showLessonsForTopicWithId(_ id: String) +} + final class TopicsPresenterImpl: TopicsPresenter { private weak var view: TopicsView? + private weak var router: TopicsRouter? private var graph: KnowledgeGraph private let userRegistrationService: UserRegistrationService private let graphService: GraphService - init(view: TopicsView, model: KnowledgeGraph, + init(view: TopicsView, model: KnowledgeGraph, router: TopicsRouter, userRegistrationService: UserRegistrationService, graphService: GraphService) { self.view = view self.graph = model + self.router = router self.userRegistrationService = userRegistrationService self.graphService = graphService } @@ -30,7 +36,7 @@ final class TopicsPresenterImpl: TopicsPresenter { func selectTopic(with viewData: TopicsViewData) { guard let topic = graph[viewData.id]?.key else { return } - showLessons(for: topic) + router?.showLessonsForTopicWithId(topic.id) } // MARK: - Private API @@ -63,10 +69,6 @@ final class TopicsPresenterImpl: TopicsPresenter { view?.displayError(title: NSLocalizedString("Error", comment: ""), message: error.localizedDescription) } - private func showLessons(for vertex: KnowledgeGraphVertex) { - print("\(#function) \(vertex.title)") - } - private func viewTopicsFrom(_ vertices: [KnowledgeGraphVertex]) -> [TopicsViewData] { return vertices.map { viewTopicFromVertex($0) } } diff --git a/ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicTableViewCell/TopicTableViewCell.swift b/ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicTableViewCell/TopicTableViewCell.swift similarity index 100% rename from ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicTableViewCell/TopicTableViewCell.swift rename to ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicTableViewCell/TopicTableViewCell.swift diff --git a/ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicTableViewCell/TopicTableViewCell.xib b/ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicTableViewCell/TopicTableViewCell.xib similarity index 100% rename from ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicTableViewCell/TopicTableViewCell.xib rename to ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicTableViewCell/TopicTableViewCell.xib diff --git a/ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicsTableViewController/TopicsTableViewController.swift b/ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicsTableViewController/TopicsTableViewController.swift similarity index 100% rename from ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicsTableViewController/TopicsTableViewController.swift rename to ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicsTableViewController/TopicsTableViewController.swift diff --git a/ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicsTableViewController/TopicsTableViewController.xib b/ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicsTableViewController/TopicsTableViewController.xib similarity index 100% rename from ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicsTableViewController/TopicsTableViewController.xib rename to ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicsTableViewController/TopicsTableViewController.xib diff --git a/ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicsView.swift b/ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicsView.swift similarity index 100% rename from ExamEGERussian/Sources/PresentationLayer/UserStories/Topics/View/TopicsView.swift rename to ExamEGERussian/Sources/PresentationLayer/Topics/View/TopicsView.swift diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index 8b9692cda6..ce1852908b 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -1898,6 +1898,10 @@ 2C23C5E61F6BFA8800FC2B7C /* RegistrationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C23C5E51F6BFA8800FC2B7C /* RegistrationPresenter.swift */; }; 2C2485472101D91F006F8858 /* RestorableBackgroundDownloaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2485462101D91F006F8858 /* RestorableBackgroundDownloaderProtocol.swift */; }; 2C2485492101EE3E006F8858 /* DownloaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2485482101EE3E006F8858 /* DownloaderTests.swift */; }; + 2C2FD77A2107359E00609621 /* LessonsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD7782107359E00609621 /* LessonsTableViewController.swift */; }; + 2C2FD77B2107359E00609621 /* LessonsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C2FD7792107359E00609621 /* LessonsTableViewController.xib */; }; + 2C2FD77F210735E000609621 /* LessonTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD77D210735E000609621 /* LessonTableViewCell.swift */; }; + 2C2FD780210735E000609621 /* LessonTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C2FD77E210735E000609621 /* LessonTableViewCell.xib */; }; 2C315E6D1F0A947D0039E4F0 /* CodeLanguagePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081BF3461EFEF630002F84AA /* CodeLanguagePickerViewController.swift */; }; 2C315E6E1F0A947D0039E4F0 /* LessonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089984281ECDE188005C0B27 /* LessonViewController.swift */; }; 2C315E6F1F0A947D0039E4F0 /* LessonPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0899842B1ECDE194005C0B27 /* LessonPresenter.swift */; }; @@ -5582,6 +5586,10 @@ 2C2485462101D91F006F8858 /* RestorableBackgroundDownloaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorableBackgroundDownloaderProtocol.swift; sourceTree = ""; }; 2C2485482101EE3E006F8858 /* DownloaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloaderTests.swift; sourceTree = ""; }; 2C2544081F3480BE004DB3D9 /* AchievementNotificationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementNotificationView.swift; sourceTree = ""; }; + 2C2FD7782107359E00609621 /* LessonsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsTableViewController.swift; sourceTree = ""; }; + 2C2FD7792107359E00609621 /* LessonsTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LessonsTableViewController.xib; sourceTree = ""; }; + 2C2FD77D210735E000609621 /* LessonTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonTableViewCell.swift; sourceTree = ""; }; + 2C2FD77E210735E000609621 /* LessonTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LessonTableViewCell.xib; sourceTree = ""; }; 2C33CBDA20EC2C2E009956B0 /* UserRegistrationServiceImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRegistrationServiceImplementation.swift; sourceTree = ""; }; 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveAchievementsPresenter.swift; sourceTree = ""; }; 2C35C4C61F4DA462002F3BF4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ru; path = LeaderboardNames/ru.lproj/adjectives_f.plist; sourceTree = ""; }; @@ -8388,6 +8396,41 @@ name = AchievementNotificationView; sourceTree = ""; }; + 2C2FD7762107355D00609621 /* Lessons */ = { + isa = PBXGroup; + children = ( + 2C2FD7772107357800609621 /* View */, + ); + path = Lessons; + sourceTree = ""; + }; + 2C2FD7772107357800609621 /* View */ = { + isa = PBXGroup; + children = ( + 2C2FD77C210735AE00609621 /* LessonsTableViewController */, + 2C2FD781210735EB00609621 /* LessonTableViewCell */, + ); + path = View; + sourceTree = ""; + }; + 2C2FD77C210735AE00609621 /* LessonsTableViewController */ = { + isa = PBXGroup; + children = ( + 2C2FD7782107359E00609621 /* LessonsTableViewController.swift */, + 2C2FD7792107359E00609621 /* LessonsTableViewController.xib */, + ); + path = LessonsTableViewController; + sourceTree = ""; + }; + 2C2FD781210735EB00609621 /* LessonTableViewCell */ = { + isa = PBXGroup; + children = ( + 2C2FD77D210735E000609621 /* LessonTableViewCell.swift */, + 2C2FD77E210735E000609621 /* LessonTableViewCell.xib */, + ); + path = LessonTableViewCell; + sourceTree = ""; + }; 2C32BAEC1F9636520003DD91 /* Utils */ = { isa = PBXGroup; children = ( @@ -8475,14 +8518,6 @@ path = AppDelegate; sourceTree = ""; }; - 2C59F05820EB8E3700666EE0 /* UserStories */ = { - isa = PBXGroup; - children = ( - 2C59F06420EBA1EB00666EE0 /* Topics */, - ); - path = UserStories; - sourceTree = ""; - }; 2C59F05A20EB8E5C00666EE0 /* SupportingFiles */ = { isa = PBXGroup; children = ( @@ -8496,7 +8531,8 @@ 2C59F05C20EB8F0D00666EE0 /* PresentationLayer */ = { isa = PBXGroup; children = ( - 2C59F05820EB8E3700666EE0 /* UserStories */, + 2C59F06420EBA1EB00666EE0 /* Topics */, + 2C2FD7762107355D00609621 /* Lessons */, 2C1BF0AF2107328900EA83CA /* LaunchScreen.storyboard */, ); path = PresentationLayer; @@ -10854,9 +10890,11 @@ 2CFE248120ED112100AF1E3D /* Auth.plist in Resources */, 2CFE247C20ECF7F700AF1E3D /* Config.plist in Resources */, 2CE3C2BA21007D3D003AB54A /* TopicsTableViewController.xib in Resources */, + 2C2FD77B2107359E00609621 /* LessonsTableViewController.xib in Resources */, 2C59F03120EB8AC900666EE0 /* Assets.xcassets in Resources */, 2CCB5A3E2100824F0091A605 /* TopicTableViewCell.xib in Resources */, 2C1BF0B02107328900EA83CA /* LaunchScreen.storyboard in Resources */, + 2C2FD780210735E000609621 /* LessonTableViewCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -14814,6 +14852,7 @@ 2CCB5A452100849A0091A605 /* UIViewController+Alert.swift in Sources */, 2C9499F220ECC0BA0049E9A8 /* DeleteDeviceExecutableTask.swift in Sources */, 2C33CC0D20EC33FF009956B0 /* CreateRequestMaker.swift in Sources */, + 2C2FD77A2107359E00609621 /* LessonsTableViewController.swift in Sources */, 2C9499C820ECBC6D0049E9A8 /* StepikModelView.swift in Sources */, 2C9499E220ECBE650049E9A8 /* NSDateExtensions.swift in Sources */, 2C9499FA20ECC1690049E9A8 /* PersistentTaskRecoveryManager.swift in Sources */, @@ -14881,6 +14920,7 @@ 2C9499D320ECBCE10049E9A8 /* MatchingReply.swift in Sources */, 2C9499AE20ECBA670049E9A8 /* LastStep.swift in Sources */, 2C94999D20ECB9080049E9A8 /* CoursePlainEntity.swift in Sources */, + 2C2FD77F210735E000609621 /* LessonTableViewCell.swift in Sources */, 2C33CBD920EC2BBD009956B0 /* UserRegistrationService.swift in Sources */, 2C949A3320ECC70D0049E9A8 /* AchievementProgressesAPI.swift in Sources */, 2C9499C520ECBC0F0049E9A8 /* PathManager.swift in Sources */, From 8e483bb41e7ec4188bbce8c4f7ba94c3a9f87a12 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 24 Jul 2018 17:19:07 +0300 Subject: [PATCH 03/14] Create lessons service --- .../LessonsService/LessonsService.swift | 14 ++++ .../LessonsService/LessonsServiceImpl.swift | 31 ++++++++ ...ft => KnowledgeGraphGoalPlainObject.swift} | 2 +- ... => KnowledgeGraphLessonPlainObject.swift} | 2 +- .../KnowledgeGraphPlainObject.swift | 14 ++-- ...t => KnowledgeGraphTopicPlainObject.swift} | 2 +- ... KnowledgeGraphTopicsMapPlainObject.swift} | 6 +- .../PlainObjects/LessonPlainObject.swift | 15 ++++ .../LessonsTableViewController.swift | 77 +++---------------- .../KnowledgeGraphPlainObject+Create.swift | 18 ++--- .../Helpers/Mocks/TopicsRouterMock.swift | 15 ++++ .../TopicsPresenterTests.swift | 1 + .../TopicsTableViewControllerTests.swift | 1 + Stepic.xcodeproj/project.pbxproj | 58 ++++++++++---- 14 files changed, 150 insertions(+), 106 deletions(-) create mode 100644 ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift create mode 100644 ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift rename ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/{GoalPlainObject.swift => KnowledgeGraphGoalPlainObject.swift} (94%) rename ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/{LessonPlainObject.swift => KnowledgeGraphLessonPlainObject.swift} (87%) rename ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/{TopicPlainObject.swift => KnowledgeGraphTopicPlainObject.swift} (94%) rename ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/{TopicsMapPlainObject.swift => KnowledgeGraphTopicsMapPlainObject.swift} (59%) create mode 100644 ExamEGERussian/Sources/Model/PlainObjects/LessonPlainObject.swift create mode 100644 ExamEGERussianTests/Helpers/Mocks/TopicsRouterMock.swift diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift new file mode 100644 index 0000000000..4b1ea21a1a --- /dev/null +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift @@ -0,0 +1,14 @@ +// +// LessonsService.swift +// ExamEGERussian +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit + +protocol LessonsService: class { + func obtainLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> +} diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift new file mode 100644 index 0000000000..8980cbd4e6 --- /dev/null +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift @@ -0,0 +1,31 @@ +// +// LessonsServiceImpl.swift +// ExamEGERussian +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit +import Alamofire + +final class LessonsServiceImpl: LessonsService { + private struct Response: Codable { + let lessons: [LessonPlainObject] + } + + func obtainLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> { + let url = "\(StepicApplicationsInfo.apiURL)/lessons" + let parameters = ["ids": ids] + let headers = AuthInfo.shared.initialHTTPHeaders + + return firstly { + Alamofire + .request(url, parameters: parameters, headers: headers) + .responseDecodable(Response.self) + }.then { response in + Promise.value(response.lessons) + } + } +} diff --git a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/GoalPlainObject.swift b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphGoalPlainObject.swift similarity index 94% rename from ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/GoalPlainObject.swift rename to ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphGoalPlainObject.swift index cad1f66b0f..09805f8575 100755 --- a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/GoalPlainObject.swift +++ b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphGoalPlainObject.swift @@ -8,7 +8,7 @@ import Foundation -struct GoalPlainObject: Codable { +struct KnowledgeGraphGoalPlainObject: Codable { let title: String let id: String let requiredTopics: [String] diff --git a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/LessonPlainObject.swift b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphLessonPlainObject.swift similarity index 87% rename from ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/LessonPlainObject.swift rename to ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphLessonPlainObject.swift index d63df8e183..4c59e0cb8e 100755 --- a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/LessonPlainObject.swift +++ b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphLessonPlainObject.swift @@ -8,7 +8,7 @@ import Foundation -struct LessonPlainObject: Codable { +struct KnowledgeGraphLessonPlainObject: Codable { let id: Int let type: String let course: String diff --git a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphPlainObject.swift b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphPlainObject.swift index 351a49f0ed..fae3996edb 100755 --- a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphPlainObject.swift +++ b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphPlainObject.swift @@ -9,9 +9,9 @@ import Foundation struct KnowledgeGraphPlainObject: Codable { - let goals: [GoalPlainObject] - let topics: [TopicPlainObject] - let topicsMap: [TopicsMapPlainObject] + let goals: [KnowledgeGraphGoalPlainObject] + let topics: [KnowledgeGraphTopicPlainObject] + let topicsMap: [KnowledgeGraphTopicsMapPlainObject] enum CodingKeys: String, CodingKey { case goals @@ -19,7 +19,7 @@ struct KnowledgeGraphPlainObject: Codable { case topicsMap = "topics-map" } - init(goals: [GoalPlainObject], topics: [TopicPlainObject], topicsMap: [TopicsMapPlainObject]) { + init(goals: [KnowledgeGraphGoalPlainObject], topics: [KnowledgeGraphTopicPlainObject], topicsMap: [KnowledgeGraphTopicsMapPlainObject]) { self.goals = goals self.topics = topics self.topicsMap = topicsMap @@ -27,8 +27,8 @@ struct KnowledgeGraphPlainObject: Codable { init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - goals = try container.decode([GoalPlainObject].self, forKey: .goals) - topics = try container.decode([TopicPlainObject].self, forKey: .topics) - topicsMap = try container.decode([TopicsMapPlainObject].self, forKey: .topicsMap) + goals = try container.decode([KnowledgeGraphGoalPlainObject].self, forKey: .goals) + topics = try container.decode([KnowledgeGraphTopicPlainObject].self, forKey: .topics) + topicsMap = try container.decode([KnowledgeGraphTopicsMapPlainObject].self, forKey: .topicsMap) } } diff --git a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/TopicPlainObject.swift b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphTopicPlainObject.swift similarity index 94% rename from ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/TopicPlainObject.swift rename to ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphTopicPlainObject.swift index f74bd6cb23..131514dd50 100755 --- a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/TopicPlainObject.swift +++ b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphTopicPlainObject.swift @@ -8,7 +8,7 @@ import Foundation -struct TopicPlainObject: Codable { +struct KnowledgeGraphTopicPlainObject: Codable { let id: String let title: String let requiredFor: String? diff --git a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/TopicsMapPlainObject.swift b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphTopicsMapPlainObject.swift similarity index 59% rename from ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/TopicsMapPlainObject.swift rename to ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphTopicsMapPlainObject.swift index 3fef1898fe..097d100c1e 100755 --- a/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/TopicsMapPlainObject.swift +++ b/ExamEGERussian/Sources/Model/PlainObjects/KnowledgeGraph/KnowledgeGraphTopicsMapPlainObject.swift @@ -8,11 +8,11 @@ import Foundation -struct TopicsMapPlainObject: Codable { +struct KnowledgeGraphTopicsMapPlainObject: Codable { let id: String - let lessons: [LessonPlainObject] + let lessons: [KnowledgeGraphLessonPlainObject] - init(id: String, lessons: [LessonPlainObject]) { + init(id: String, lessons: [KnowledgeGraphLessonPlainObject]) { self.id = id self.lessons = lessons } diff --git a/ExamEGERussian/Sources/Model/PlainObjects/LessonPlainObject.swift b/ExamEGERussian/Sources/Model/PlainObjects/LessonPlainObject.swift new file mode 100644 index 0000000000..c1fc7233b0 --- /dev/null +++ b/ExamEGERussian/Sources/Model/PlainObjects/LessonPlainObject.swift @@ -0,0 +1,15 @@ +// +// LessonPlainObject.swift +// ExamEGERussian +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation + +struct LessonPlainObject: Codable { + let id: Int + let steps: [Int] + let title: String +} diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift index 65efc41768..a39a4ecfc6 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift @@ -8,88 +8,31 @@ import UIKit -class LessonsTableViewController: UITableViewController { - +final class LessonsTableViewController: UITableViewController { + override func viewDidLoad() { super.viewDidLoad() - // Uncomment the following line to preserve selection between presentations - // self.clearsSelectionOnViewWillAppear = false - - // Uncomment the following line to display an Edit button in the navigation bar for this view controller. - // self.navigationItem.rightBarButtonItem = self.editButtonItem - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. + tableView.registerNib(for: LessonTableViewCell.self) } - // MARK: - Table view data source - - override func numberOfSections(in tableView: UITableView) -> Int { - // #warning Incomplete implementation, return the number of sections - return 0 - } + // MARK: - UITableViewDataSource override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // #warning Incomplete implementation, return the number of rows - return 0 + return 10 } - /* override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) - - // Configure the cell... + let cell: LessonTableViewCell = tableView.dequeueReusableCell(for: indexPath) + cell.descriptionTitleLabel.text = "Title for row: \(indexPath.row)" return cell } - */ - /* - // Override to support conditional editing of the table view. - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the specified item to be editable. - return true - } - */ + // MARK: - UITableViewDelegate - /* - // Override to support editing the table view. - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete { - // Delete the row from the data source - tableView.deleteRows(at: [indexPath], with: .fade) - } else if editingStyle == .insert { - // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view - } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) } - */ - /* - // Override to support rearranging the table view. - override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { - - } - */ - - /* - // Override to support conditional rearranging of the table view. - override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the item to be re-orderable. - return true - } - */ - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destinationViewController. - // Pass the selected object to the new view controller. - } - */ - } diff --git a/ExamEGERussianTests/Helpers/Creators/KnowledgeGraphPlainObject+Create.swift b/ExamEGERussianTests/Helpers/Creators/KnowledgeGraphPlainObject+Create.swift index c79c923163..839b2f40fc 100644 --- a/ExamEGERussianTests/Helpers/Creators/KnowledgeGraphPlainObject+Create.swift +++ b/ExamEGERussianTests/Helpers/Creators/KnowledgeGraphPlainObject+Create.swift @@ -12,21 +12,21 @@ import Foundation extension KnowledgeGraphPlainObject { static func createGraph() -> KnowledgeGraphPlainObject { let goals = [ - GoalPlainObject(title: "Морфология", id: "morph", requiredTopics: ["slitno-razdelno"]) + KnowledgeGraphGoalPlainObject(title: "Морфология", id: "morph", requiredTopics: ["slitno-razdelno"]) ] let topics = [ - TopicPlainObject(id: "slitno-razdelno", title: "B13 Слитное раздельное написание", requiredFor: nil), - TopicPlainObject(id: "pristavki", title: "B9 Приставки", requiredFor: "slitno-razdelno") + KnowledgeGraphTopicPlainObject(id: "slitno-razdelno", title: "B13 Слитное раздельное написание", requiredFor: nil), + KnowledgeGraphTopicPlainObject(id: "pristavki", title: "B9 Приставки", requiredFor: "slitno-razdelno") ] let lessons = [ - LessonPlainObject(id: 82810, type: "theory", course: "7798"), - LessonPlainObject(id: 82809, type: "theory", course: "7798"), - LessonPlainObject(id: 86295, type: "theory", course: "7798"), - LessonPlainObject(id: 86297, type: "theory", course: "7798"), - LessonPlainObject(id: 6000, type: "practice", course: "7798") + KnowledgeGraphLessonPlainObject(id: 82810, type: "theory", course: "7798"), + KnowledgeGraphLessonPlainObject(id: 82809, type: "theory", course: "7798"), + KnowledgeGraphLessonPlainObject(id: 86295, type: "theory", course: "7798"), + KnowledgeGraphLessonPlainObject(id: 86297, type: "theory", course: "7798"), + KnowledgeGraphLessonPlainObject(id: 6000, type: "practice", course: "7798") ] let topicsMap = [ - TopicsMapPlainObject(id: "slitno-razdelno", lessons: lessons) + KnowledgeGraphTopicsMapPlainObject(id: "slitno-razdelno", lessons: lessons) ] return KnowledgeGraphPlainObject(goals: goals, topics: topics, topicsMap: topicsMap) diff --git a/ExamEGERussianTests/Helpers/Mocks/TopicsRouterMock.swift b/ExamEGERussianTests/Helpers/Mocks/TopicsRouterMock.swift new file mode 100644 index 0000000000..0e0083bb56 --- /dev/null +++ b/ExamEGERussianTests/Helpers/Mocks/TopicsRouterMock.swift @@ -0,0 +1,15 @@ +// +// TopicsRouterMock.swift +// ExamEGERussianTests +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +@testable import ExamEGERussian + +final class TopicsRouterMock: TopicsRouter { + func showLessonsForTopicWithId(_ id: String) { + } +} diff --git a/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsPresenterTests.swift b/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsPresenterTests.swift index 748fdde36f..fc1087576a 100644 --- a/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsPresenterTests.swift +++ b/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsPresenterTests.swift @@ -23,6 +23,7 @@ class TopicsPresenterTests: XCTestCase { topicsPresenter = TopicsPresenterImpl( view: topicsViewSpy, model: KnowledgeGraph(), + router: TopicsRouterMock(), userRegistrationService: userRegistrationService, graphService: graphService ) diff --git a/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsTableViewControllerTests.swift b/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsTableViewControllerTests.swift index d942be48f7..e92053f14e 100644 --- a/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsTableViewControllerTests.swift +++ b/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsTableViewControllerTests.swift @@ -16,6 +16,7 @@ class TopicsTableViewControllerTests: XCTestCase { let presenter = TopicsPresenterImpl( view: vc, model: KnowledgeGraph(), + router: TopicsRouterMock(), userRegistrationService: UserRegistrationServiceMock(), graphService: GraphServiceMock() ) diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index ce1852908b..7dae9edeb5 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -1902,6 +1902,10 @@ 2C2FD77B2107359E00609621 /* LessonsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C2FD7792107359E00609621 /* LessonsTableViewController.xib */; }; 2C2FD77F210735E000609621 /* LessonTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD77D210735E000609621 /* LessonTableViewCell.swift */; }; 2C2FD780210735E000609621 /* LessonTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C2FD77E210735E000609621 /* LessonTableViewCell.xib */; }; + 2C2FD7842107448F00609621 /* LessonsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD7832107448F00609621 /* LessonsService.swift */; }; + 2C2FD7862107453800609621 /* LessonsServiceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD7852107453800609621 /* LessonsServiceImpl.swift */; }; + 2C2FD78821074FFD00609621 /* TopicsRouterMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD78721074FFD00609621 /* TopicsRouterMock.swift */; }; + 2C2FD78A2107507200609621 /* LessonPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD7892107507200609621 /* LessonPlainObject.swift */; }; 2C315E6D1F0A947D0039E4F0 /* CodeLanguagePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081BF3461EFEF630002F84AA /* CodeLanguagePickerViewController.swift */; }; 2C315E6E1F0A947D0039E4F0 /* LessonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089984281ECDE188005C0B27 /* LessonViewController.swift */; }; 2C315E6F1F0A947D0039E4F0 /* LessonPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0899842B1ECDE194005C0B27 /* LessonPresenter.swift */; }; @@ -3369,11 +3373,11 @@ 2CA3F55D20FF79860041E893 /* GraphService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F55C20FF79860041E893 /* GraphService.swift */; }; 2CA3F55F20FF7A150041E893 /* StepikResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F55E20FF7A150041E893 /* StepikResult.swift */; }; 2CA3F56120FF7C030041E893 /* GraphServiceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56020FF7C030041E893 /* GraphServiceImpl.swift */; }; - 2CA3F56720FF82970041E893 /* GoalPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56220FF82960041E893 /* GoalPlainObject.swift */; }; + 2CA3F56720FF82970041E893 /* KnowledgeGraphGoalPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56220FF82960041E893 /* KnowledgeGraphGoalPlainObject.swift */; }; 2CA3F56820FF82970041E893 /* KnowledgeGraphPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56320FF82960041E893 /* KnowledgeGraphPlainObject.swift */; }; - 2CA3F56920FF82970041E893 /* LessonPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56420FF82970041E893 /* LessonPlainObject.swift */; }; - 2CA3F56A20FF82970041E893 /* TopicsMapPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56520FF82970041E893 /* TopicsMapPlainObject.swift */; }; - 2CA3F56B20FF82970041E893 /* TopicPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56620FF82970041E893 /* TopicPlainObject.swift */; }; + 2CA3F56920FF82970041E893 /* KnowledgeGraphLessonPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56420FF82970041E893 /* KnowledgeGraphLessonPlainObject.swift */; }; + 2CA3F56A20FF82970041E893 /* KnowledgeGraphTopicsMapPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56520FF82970041E893 /* KnowledgeGraphTopicsMapPlainObject.swift */; }; + 2CA3F56B20FF82970041E893 /* KnowledgeGraphTopicPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56620FF82970041E893 /* KnowledgeGraphTopicPlainObject.swift */; }; 2CA5E5A5200E1FC500CE77B0 /* AdaptiveStatsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C733C391F29E090000E7FAF /* AdaptiveStatsManager.swift */; }; 2CA5E5A6200E1FC700CE77B0 /* AdaptiveRatingHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEDA35F1F336FEC005F4A5D /* AdaptiveRatingHelper.swift */; }; 2CA5E5A7200E1FCA00CE77B0 /* AdaptiveRatingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C76ACCC1F16496C0077D9D7 /* AdaptiveRatingManager.swift */; }; @@ -5590,6 +5594,10 @@ 2C2FD7792107359E00609621 /* LessonsTableViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LessonsTableViewController.xib; sourceTree = ""; }; 2C2FD77D210735E000609621 /* LessonTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonTableViewCell.swift; sourceTree = ""; }; 2C2FD77E210735E000609621 /* LessonTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LessonTableViewCell.xib; sourceTree = ""; }; + 2C2FD7832107448F00609621 /* LessonsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsService.swift; sourceTree = ""; }; + 2C2FD7852107453800609621 /* LessonsServiceImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsServiceImpl.swift; sourceTree = ""; }; + 2C2FD78721074FFD00609621 /* TopicsRouterMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicsRouterMock.swift; sourceTree = ""; }; + 2C2FD7892107507200609621 /* LessonPlainObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonPlainObject.swift; sourceTree = ""; }; 2C33CBDA20EC2C2E009956B0 /* UserRegistrationServiceImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRegistrationServiceImplementation.swift; sourceTree = ""; }; 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveAchievementsPresenter.swift; sourceTree = ""; }; 2C35C4C61F4DA462002F3BF4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ru; path = LeaderboardNames/ru.lproj/adjectives_f.plist; sourceTree = ""; }; @@ -5717,11 +5725,11 @@ 2CA3F55C20FF79860041E893 /* GraphService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphService.swift; sourceTree = ""; }; 2CA3F55E20FF7A150041E893 /* StepikResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepikResult.swift; sourceTree = ""; }; 2CA3F56020FF7C030041E893 /* GraphServiceImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphServiceImpl.swift; sourceTree = ""; }; - 2CA3F56220FF82960041E893 /* GoalPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GoalPlainObject.swift; sourceTree = ""; }; + 2CA3F56220FF82960041E893 /* KnowledgeGraphGoalPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnowledgeGraphGoalPlainObject.swift; sourceTree = ""; }; 2CA3F56320FF82960041E893 /* KnowledgeGraphPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnowledgeGraphPlainObject.swift; sourceTree = ""; }; - 2CA3F56420FF82970041E893 /* LessonPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LessonPlainObject.swift; sourceTree = ""; }; - 2CA3F56520FF82970041E893 /* TopicsMapPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopicsMapPlainObject.swift; sourceTree = ""; }; - 2CA3F56620FF82970041E893 /* TopicPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopicPlainObject.swift; sourceTree = ""; }; + 2CA3F56420FF82970041E893 /* KnowledgeGraphLessonPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnowledgeGraphLessonPlainObject.swift; sourceTree = ""; }; + 2CA3F56520FF82970041E893 /* KnowledgeGraphTopicsMapPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnowledgeGraphTopicsMapPlainObject.swift; sourceTree = ""; }; + 2CA3F56620FF82970041E893 /* KnowledgeGraphTopicPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnowledgeGraphTopicPlainObject.swift; sourceTree = ""; }; 2CA8D3872088D9C000E105E9 /* Adaptive 8290.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Adaptive 8290.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2CA9D97620109718007AA743 /* AdaptiveStatsPagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveStatsPagerViewController.swift; sourceTree = ""; }; 2CA9D97820109727007AA743 /* AdaptiveStatsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveStatsPresenter.swift; sourceTree = ""; }; @@ -8290,6 +8298,7 @@ isa = PBXGroup; children = ( 2C16542720FF8A7000E91F80 /* KnowledgeGraph */, + 2C2FD7892107507200609621 /* LessonPlainObject.swift */, ); path = PlainObjects; sourceTree = ""; @@ -8298,10 +8307,10 @@ isa = PBXGroup; children = ( 2CA3F56320FF82960041E893 /* KnowledgeGraphPlainObject.swift */, - 2CA3F56220FF82960041E893 /* GoalPlainObject.swift */, - 2CA3F56620FF82970041E893 /* TopicPlainObject.swift */, - 2CA3F56520FF82970041E893 /* TopicsMapPlainObject.swift */, - 2CA3F56420FF82970041E893 /* LessonPlainObject.swift */, + 2CA3F56220FF82960041E893 /* KnowledgeGraphGoalPlainObject.swift */, + 2CA3F56620FF82970041E893 /* KnowledgeGraphTopicPlainObject.swift */, + 2CA3F56520FF82970041E893 /* KnowledgeGraphTopicsMapPlainObject.swift */, + 2CA3F56420FF82970041E893 /* KnowledgeGraphLessonPlainObject.swift */, ); path = KnowledgeGraph; sourceTree = ""; @@ -8431,6 +8440,15 @@ path = LessonTableViewCell; sourceTree = ""; }; + 2C2FD7822107447300609621 /* LessonsService */ = { + isa = PBXGroup; + children = ( + 2C2FD7832107448F00609621 /* LessonsService.swift */, + 2C2FD7852107453800609621 /* LessonsServiceImpl.swift */, + ); + path = LessonsService; + sourceTree = ""; + }; 2C32BAEC1F9636520003DD91 /* Utils */ = { isa = PBXGroup; children = ( @@ -8595,8 +8613,9 @@ 2C59F06320EB9FFA00666EE0 /* Services */ = { isa = PBXGroup; children = ( - 2C59F06520EBA26C00666EE0 /* UserRegistrationService */, 62E9862365BD47119FA0E1D9 /* GraphService */, + 2C2FD7822107447300609621 /* LessonsService */, + 2C59F06520EBA26C00666EE0 /* UserRegistrationService */, ); path = Services; sourceTree = ""; @@ -9185,6 +9204,7 @@ isa = PBXGroup; children = ( 2CCB5A632100A5D60091A605 /* GraphServiceMock.swift */, + 2C2FD78721074FFD00609621 /* TopicsRouterMock.swift */, 2CF2246620EF6D900093D844 /* UserRegistrationServiceMock.swift */, ); path = Mocks; @@ -14769,6 +14789,7 @@ 2C9499DC20ECBD9D0049E9A8 /* Comment.swift in Sources */, 2C9499E420ECBE8C0049E9A8 /* CodeLimit.swift in Sources */, 2C9499EF20ECC0580049E9A8 /* Notification+FetchMethods.swift in Sources */, + 2C2FD7842107448F00609621 /* LessonsService.swift in Sources */, 2C949A0C20ECC3FC0049E9A8 /* StreaksStepikAlertManager.swift in Sources */, 2C94999220ECB7E30049E9A8 /* Unit.swift in Sources */, 2C9499FF20ECC26F0049E9A8 /* StepOptions+CoreDataProperties.swift in Sources */, @@ -14811,7 +14832,7 @@ 2C33CC0F20EC340D009956B0 /* Assignment.swift in Sources */, 2C949A4420ECCA000049E9A8 /* TooltipDefaultsManager.swift in Sources */, 2C949A2E20ECC6C40049E9A8 /* PlaceholderTableViewCell.swift in Sources */, - 2CA3F56920FF82970041E893 /* LessonPlainObject.swift in Sources */, + 2CA3F56920FF82970041E893 /* KnowledgeGraphLessonPlainObject.swift in Sources */, 2C949A6E20ECD0D10049E9A8 /* AchievementsListTableViewCell.swift in Sources */, 2C9499B220ECBAC00049E9A8 /* ChoiceDataset.swift in Sources */, 2C33CBFA20EC32B0009956B0 /* AssignmentsAPI.swift in Sources */, @@ -14958,11 +14979,11 @@ 2C9499C720ECBC3C0049E9A8 /* DiscussionProxy.swift in Sources */, 2C33CBDC20EC2DB4009956B0 /* ApiRequest.swift in Sources */, 2C9499DF20ECBDEC0049E9A8 /* StepikURLSessionConfiguration.swift in Sources */, - 2CA3F56A20FF82970041E893 /* TopicsMapPlainObject.swift in Sources */, + 2CA3F56A20FF82970041E893 /* KnowledgeGraphTopicsMapPlainObject.swift in Sources */, 2C33CC0C20EC33F7009956B0 /* DeleteRequestMaker.swift in Sources */, 2C949A0D20ECC4070049E9A8 /* StreaksAlertPresentationManager.swift in Sources */, 2C9499DE20ECBDE50049E9A8 /* AlamofireDefaultSessionManager.swift in Sources */, - 2CA3F56720FF82970041E893 /* GoalPlainObject.swift in Sources */, + 2CA3F56720FF82970041E893 /* KnowledgeGraphGoalPlainObject.swift in Sources */, 2C9499C120ECBB950049E9A8 /* StorageData.swift in Sources */, 2CE84BE120F4FD030069B869 /* GraphPathFinder.swift in Sources */, 2C9499C620ECBC250049E9A8 /* VideoDownloadDelegate.swift in Sources */, @@ -15028,10 +15049,11 @@ 2C949A1920ECC54F0049E9A8 /* StepikTableView.swift in Sources */, 2C949A6A20ECD0400049E9A8 /* AchievementDescription.swift in Sources */, 2C949A7220ECD1820049E9A8 /* Skeletonable+UIView.swift in Sources */, - 2CA3F56B20FF82970041E893 /* TopicPlainObject.swift in Sources */, + 2CA3F56B20FF82970041E893 /* KnowledgeGraphTopicPlainObject.swift in Sources */, 2C949A0F20ECC4360049E9A8 /* StepikLabel.swift in Sources */, 2C9499EB20ECBFE90049E9A8 /* NotificationRegistrator.swift in Sources */, 2C33CBFF20EC3336009956B0 /* AttemptsAPI.swift in Sources */, + 2C2FD7862107453800609621 /* LessonsServiceImpl.swift in Sources */, 2C94999F20ECB9220049E9A8 /* Step+CoreDataProperties.swift in Sources */, 2C33CC0220EC334C009956B0 /* NotificationsAPI.swift in Sources */, 2CA3F56120FF7C030041E893 /* GraphServiceImpl.swift in Sources */, @@ -15055,6 +15077,7 @@ 2CCB5A682100B8E50091A605 /* KnowledgeGraphVertex.swift in Sources */, 2C9499E320ECBE860049E9A8 /* StepOptions.swift in Sources */, 2C33CBE820EC3160009956B0 /* AnalyticsEvents.swift in Sources */, + 2C2FD78A2107507200609621 /* LessonPlainObject.swift in Sources */, 2C949A5120ECCDD10049E9A8 /* EmailAuthViewController.swift in Sources */, 2C94998B20ECB7080049E9A8 /* UIColorExtensions.swift in Sources */, 2C9499FE20ECC2530049E9A8 /* PlaybackCommandEntity.swift in Sources */, @@ -15107,6 +15130,7 @@ 2CC9955E2105C0AD0023EC0C /* TopicsPresenterTests.swift in Sources */, 2CE84BE420F505980069B869 /* GraphPathFinderTests.swift in Sources */, 2C6841C420EE14E00050155F /* UserRegistrationServiceTests.swift in Sources */, + 2C2FD78821074FFD00609621 /* TopicsRouterMock.swift in Sources */, 2C8305F720F390F8003D7F9B /* GraphTests.swift in Sources */, 2CCB5A642100A5D60091A605 /* GraphServiceMock.swift in Sources */, 2C6841CA20EE1F7D0050155F /* ServiceComponentsAssemblyTestsHelper.swift in Sources */, From 9c35d6dc134e72685f0564766bb36c8c8bcd643b Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 24 Jul 2018 17:19:55 +0300 Subject: [PATCH 04/14] Display lessons --- .../RootNavigationManager.swift | 12 +++- .../ServiceComponents.swift | 1 + .../ServiceComponentsAssembly.swift | 4 ++ .../Lessons/Presenter/LessonsPresenter.swift | 13 +++++ .../Presenter/LessonsPresenterImpl.swift | 58 +++++++++++++++++++ .../LessonTableViewCell.xib | 3 +- .../LessonsTableViewController.swift | 28 ++++++++- .../Lessons/View/LessonsView.swift | 19 ++++++ .../Presenter/TopicsPresenterImpl.swift | 12 ++-- Stepic.xcodeproj/project.pbxproj | 20 +++++++ 10 files changed, 160 insertions(+), 10 deletions(-) create mode 100644 ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenter.swift create mode 100644 ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift create mode 100644 ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsView.swift diff --git a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift index 80c6453617..d58e410d7f 100644 --- a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift +++ b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift @@ -14,6 +14,7 @@ final class RootNavigationManager { private unowned let serviceComponents: ServiceComponents private weak var navigationController: UINavigationController? + private let knowledgeGraph = KnowledgeGraph() // MARK: Init @@ -27,7 +28,7 @@ final class RootNavigationManager { let controller = TopicsTableViewController() controller.presenter = TopicsPresenterImpl( view: controller, - model: KnowledgeGraph(), + knowledgeGraph: knowledgeGraph, router: self, userRegistrationService: serviceComponents.userRegistrationService, graphService: serviceComponents.graphService @@ -45,6 +46,15 @@ final class RootNavigationManager { extension RootNavigationManager: TopicsRouter { func showLessonsForTopicWithId(_ id: String) { let controller = LessonsTableViewController() + let presenter = LessonsPresenterImpl( + view: controller, + topicId: id, + knowledgeGraph: knowledgeGraph, + lessonsService: serviceComponents.lessonsService + ) + controller.presenter = presenter + controller.title = knowledgeGraph[id]?.key.title + navigationController?.pushViewController(controller, animated: true) } } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift index 4333e766b4..bbfa769249 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift @@ -11,4 +11,5 @@ import Foundation protocol ServiceComponents: class { var userRegistrationService: UserRegistrationService { get } var graphService: GraphService { get } + var lessonsService: LessonsService { get } } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift index 3623773160..431de41a38 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift @@ -41,4 +41,8 @@ final class ServiceComponentsAssembly: ServiceComponents { var graphService: GraphService { return GraphServiceImpl() } + + var lessonsService: LessonsService { + return LessonsServiceImpl() + } } diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenter.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenter.swift new file mode 100644 index 0000000000..6adae8c928 --- /dev/null +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenter.swift @@ -0,0 +1,13 @@ +// +// LessonsPresenter.swift +// ExamEGERussian +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation + +protocol LessonsPresenter: class { + func refresh() +} diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift new file mode 100644 index 0000000000..c3b9a82710 --- /dev/null +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift @@ -0,0 +1,58 @@ +// +// LessonsPresenterImpl.swift +// ExamEGERussian +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit + +final class LessonsPresenterImpl: LessonsPresenter { + + private weak var view: LessonsView? + private let topicId: String + private let knowledgeGraph: KnowledgeGraph + private var lessons = [LessonPlainObject]() + private let lessonsService: LessonsService + + private var lessonsIds: [Int] { + guard let topic = knowledgeGraph[topicId]?.key else { return [] } + return topic.lessons.filter { $0.type == .theory }.map { $0.id } + } + + init(view: LessonsView, topicId: String, knowledgeGraph: KnowledgeGraph, + lessonsService: LessonsService) { + self.view = view + self.topicId = topicId + self.knowledgeGraph = knowledgeGraph + self.lessonsService = lessonsService + } + + func refresh() { + fetchLessons() + } + + // MARK: - Private API + + private func fetchLessons() { + guard lessonsIds.count > 0 else { return } + lessonsService.obtainLessons(with: lessonsIds).done { [weak self] responseModel in + guard let `self` = self else { return } + self.lessons = responseModel + let viewData = self.viewLessons(from: self.lessons) + self.view?.setLessons(viewData) + }.catch { [weak self] error in + self?.displayError(error) + } + } + + private func viewLessons(from lessons: [LessonPlainObject]) -> [LessonsViewData] { + return lessons.map { LessonsViewData(id: $0.id, title: $0.title) } + } + + private func displayError(_ error: Swift.Error) { + view?.displayError(title: NSLocalizedString("Error", comment: ""), message: error.localizedDescription) + } +} diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.xib b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.xib index a8aec1d898..0736771c72 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.xib +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonTableViewCell/LessonTableViewCell.xib @@ -20,13 +20,14 @@ + diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift index a39a4ecfc6..aaf294ed8e 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift @@ -10,21 +10,34 @@ import UIKit final class LessonsTableViewController: UITableViewController { + // MARK: Instance Properties + + var presenter: LessonsPresenter! + + private var lessons = [LessonsViewData]() { + didSet { + tableView.reloadData() + } + } + + // MARK: - UIViewController Lifecycle + override func viewDidLoad() { super.viewDidLoad() tableView.registerNib(for: LessonTableViewCell.self) + presenter.refresh() } // MARK: - UITableViewDataSource override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 10 + return lessons.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: LessonTableViewCell = tableView.dequeueReusableCell(for: indexPath) - cell.descriptionTitleLabel.text = "Title for row: \(indexPath.row)" + cell.descriptionTitleLabel.text = lessons[indexPath.row].title return cell } @@ -34,5 +47,16 @@ final class LessonsTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } +} + +// MARK: - LessonsTableViewController: LessonsView - +extension LessonsTableViewController: LessonsView { + func setLessons(_ lessons: [LessonsViewData]) { + self.lessons = lessons + } + + func displayError(title: String, message: String) { + presentAlert(withTitle: title, message: message) + } } diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsView.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsView.swift new file mode 100644 index 0000000000..44a51e76f2 --- /dev/null +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsView.swift @@ -0,0 +1,19 @@ +// +// LessonsView.swift +// ExamEGERussian +// +// Created by Ivan Magda on 24/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation + +struct LessonsViewData { + let id: Int + let title: String +} + +protocol LessonsView: class { + func setLessons(_ lessons: [LessonsViewData]) + func displayError(title: String, message: String) +} diff --git a/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift index fcd939932b..8d7f0bfb8d 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift @@ -16,14 +16,14 @@ final class TopicsPresenterImpl: TopicsPresenter { private weak var view: TopicsView? private weak var router: TopicsRouter? - private var graph: KnowledgeGraph + private let knowledgeGraph: KnowledgeGraph private let userRegistrationService: UserRegistrationService private let graphService: GraphService - init(view: TopicsView, model: KnowledgeGraph, router: TopicsRouter, + init(view: TopicsView, knowledgeGraph: KnowledgeGraph, router: TopicsRouter, userRegistrationService: UserRegistrationService, graphService: GraphService) { self.view = view - self.graph = model + self.knowledgeGraph = knowledgeGraph self.router = router self.userRegistrationService = userRegistrationService self.graphService = graphService @@ -35,7 +35,7 @@ final class TopicsPresenterImpl: TopicsPresenter { } func selectTopic(with viewData: TopicsViewData) { - guard let topic = graph[viewData.id]?.key else { return } + guard let topic = knowledgeGraph[viewData.id]?.key else { return } router?.showLessonsForTopicWithId(topic.id) } @@ -56,9 +56,9 @@ final class TopicsPresenterImpl: TopicsPresenter { guard let `self` = self else { return } let builder = KnowledgeGraphBuilder(graphPlainObject: responseModel) guard let graph = builder.build() as? KnowledgeGraph else { return } - self.graph = graph + self.knowledgeGraph.adjacencies = graph.adjacencies - let vertices = graph.vertices as! [KnowledgeGraphVertex] + let vertices = self.knowledgeGraph.vertices as! [KnowledgeGraphVertex] self.view?.setTopics(self.viewTopicsFrom(vertices)) }.catch { [weak self] error in self?.displayError(error) diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index 7dae9edeb5..8b353306b4 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -1906,6 +1906,9 @@ 2C2FD7862107453800609621 /* LessonsServiceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD7852107453800609621 /* LessonsServiceImpl.swift */; }; 2C2FD78821074FFD00609621 /* TopicsRouterMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD78721074FFD00609621 /* TopicsRouterMock.swift */; }; 2C2FD78A2107507200609621 /* LessonPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD7892107507200609621 /* LessonPlainObject.swift */; }; + 2C2FD78D210753AD00609621 /* LessonsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD78C210753AD00609621 /* LessonsPresenter.swift */; }; + 2C2FD78F210753CD00609621 /* LessonsPresenterImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD78E210753CD00609621 /* LessonsPresenterImpl.swift */; }; + 2C2FD7912107540500609621 /* LessonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD7902107540500609621 /* LessonsView.swift */; }; 2C315E6D1F0A947D0039E4F0 /* CodeLanguagePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081BF3461EFEF630002F84AA /* CodeLanguagePickerViewController.swift */; }; 2C315E6E1F0A947D0039E4F0 /* LessonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089984281ECDE188005C0B27 /* LessonViewController.swift */; }; 2C315E6F1F0A947D0039E4F0 /* LessonPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0899842B1ECDE194005C0B27 /* LessonPresenter.swift */; }; @@ -5598,6 +5601,9 @@ 2C2FD7852107453800609621 /* LessonsServiceImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsServiceImpl.swift; sourceTree = ""; }; 2C2FD78721074FFD00609621 /* TopicsRouterMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopicsRouterMock.swift; sourceTree = ""; }; 2C2FD7892107507200609621 /* LessonPlainObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonPlainObject.swift; sourceTree = ""; }; + 2C2FD78C210753AD00609621 /* LessonsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsPresenter.swift; sourceTree = ""; }; + 2C2FD78E210753CD00609621 /* LessonsPresenterImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsPresenterImpl.swift; sourceTree = ""; }; + 2C2FD7902107540500609621 /* LessonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsView.swift; sourceTree = ""; }; 2C33CBDA20EC2C2E009956B0 /* UserRegistrationServiceImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRegistrationServiceImplementation.swift; sourceTree = ""; }; 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveAchievementsPresenter.swift; sourceTree = ""; }; 2C35C4C61F4DA462002F3BF4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ru; path = LeaderboardNames/ru.lproj/adjectives_f.plist; sourceTree = ""; }; @@ -8408,6 +8414,7 @@ 2C2FD7762107355D00609621 /* Lessons */ = { isa = PBXGroup; children = ( + 2C2FD78B2107539700609621 /* Presenter */, 2C2FD7772107357800609621 /* View */, ); path = Lessons; @@ -8418,6 +8425,7 @@ children = ( 2C2FD77C210735AE00609621 /* LessonsTableViewController */, 2C2FD781210735EB00609621 /* LessonTableViewCell */, + 2C2FD7902107540500609621 /* LessonsView.swift */, ); path = View; sourceTree = ""; @@ -8449,6 +8457,15 @@ path = LessonsService; sourceTree = ""; }; + 2C2FD78B2107539700609621 /* Presenter */ = { + isa = PBXGroup; + children = ( + 2C2FD78C210753AD00609621 /* LessonsPresenter.swift */, + 2C2FD78E210753CD00609621 /* LessonsPresenterImpl.swift */, + ); + path = Presenter; + sourceTree = ""; + }; 2C32BAEC1F9636520003DD91 /* Utils */ = { isa = PBXGroup; children = ( @@ -14815,6 +14832,7 @@ 2C949A5520ECCE270049E9A8 /* UITableView+TableHeaderLayout.swift in Sources */, 2C9499D220ECBCDB0049E9A8 /* SortingReply.swift in Sources */, 2C9499F020ECC0720049E9A8 /* CodeTemplate+CoreDataProperties.swift in Sources */, + 2C2FD78D210753AD00609621 /* LessonsPresenter.swift in Sources */, 2C94999620ECB86B0049E9A8 /* Assignment+CoreDataProperties.swift in Sources */, 2C949A2620ECC60F0049E9A8 /* ProfileHeaderInfoView.swift in Sources */, 2CCB5A6021009DB70091A605 /* AbstractGraphBuilder.swift in Sources */, @@ -14856,6 +14874,7 @@ 2C9499E720ECBF880049E9A8 /* CodeTemplate.swift in Sources */, 2C949A3C20ECC87A0049E9A8 /* Reachability.m in Sources */, 2C33CBF020EC325A009956B0 /* RecommendationsAPI.swift in Sources */, + 2C2FD7912107540500609621 /* LessonsView.swift in Sources */, 2C9499CB20ECBCA90049E9A8 /* Submission.swift in Sources */, 2C949A3120ECC6FE0049E9A8 /* AchievementsRetriever.swift in Sources */, 2C33CC1120EC3420009956B0 /* ContentLanguage.swift in Sources */, @@ -14883,6 +14902,7 @@ 2C949A0420ECC3000049E9A8 /* NotificationsMarkAsReadButton.swift in Sources */, 2C949A6120ECCF1A0049E9A8 /* ContentLanguagePreferenceTableViewCell.swift in Sources */, 2CCB5A55210089180091A605 /* UITableView+Registration.swift in Sources */, + 2C2FD78F210753CD00609621 /* LessonsPresenterImpl.swift in Sources */, 2C33CBFD20EC331E009956B0 /* NotificationStatusesAPI.swift in Sources */, 2C949A4A20ECCCAC0049E9A8 /* AuthRoutingManager.swift in Sources */, 2C33CBEE20EC3249009956B0 /* UnitsAPI.swift in Sources */, From 287bd012c761a474df44eecf7149550903a19f4f Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Tue, 24 Jul 2018 17:31:38 +0300 Subject: [PATCH 05/14] Handle lesson selection --- .../RootNavigationManager.swift | 9 ++++++++ .../Lessons/Presenter/LessonsPresenter.swift | 1 + .../Presenter/LessonsPresenterImpl.swift | 12 +++++++++- .../LessonsTableViewController.swift | 22 +++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift index d58e410d7f..36a38f643d 100644 --- a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift +++ b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift @@ -48,6 +48,7 @@ extension RootNavigationManager: TopicsRouter { let controller = LessonsTableViewController() let presenter = LessonsPresenterImpl( view: controller, + router: self, topicId: id, knowledgeGraph: knowledgeGraph, lessonsService: serviceComponents.lessonsService @@ -58,3 +59,11 @@ extension RootNavigationManager: TopicsRouter { navigationController?.pushViewController(controller, animated: true) } } + +// MARK: - RootNavigationManager: LessonsRouter - + +extension RootNavigationManager: LessonsRouter { + func showStepsForLessonWith(_ id: Int) { + print("\(#function) \(id)") + } +} diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenter.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenter.swift index 6adae8c928..c04c4cf787 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenter.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenter.swift @@ -10,4 +10,5 @@ import Foundation protocol LessonsPresenter: class { func refresh() + func selectLesson(with viewData: LessonsViewData) } diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift index c3b9a82710..294edffaba 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift @@ -9,9 +9,14 @@ import Foundation import PromiseKit +protocol LessonsRouter: class { + func showStepsForLessonWith(_ id: Int) +} + final class LessonsPresenterImpl: LessonsPresenter { private weak var view: LessonsView? + private weak var router: LessonsRouter? private let topicId: String private let knowledgeGraph: KnowledgeGraph private var lessons = [LessonPlainObject]() @@ -22,9 +27,10 @@ final class LessonsPresenterImpl: LessonsPresenter { return topic.lessons.filter { $0.type == .theory }.map { $0.id } } - init(view: LessonsView, topicId: String, knowledgeGraph: KnowledgeGraph, + init(view: LessonsView, router: LessonsRouter, topicId: String, knowledgeGraph: KnowledgeGraph, lessonsService: LessonsService) { self.view = view + self.router = router self.topicId = topicId self.knowledgeGraph = knowledgeGraph self.lessonsService = lessonsService @@ -34,6 +40,10 @@ final class LessonsPresenterImpl: LessonsPresenter { fetchLessons() } + func selectLesson(with viewData: LessonsViewData) { + router?.showStepsForLessonWith(viewData.id) + } + // MARK: - Private API private func fetchLessons() { diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift index aaf294ed8e..09b96738f4 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/View/LessonsTableViewController/LessonsTableViewController.swift @@ -17,15 +17,30 @@ final class LessonsTableViewController: UITableViewController { private var lessons = [LessonsViewData]() { didSet { tableView.reloadData() + topicsRefreshControl.endRefreshing() } } + private lazy var topicsRefreshControl: UIRefreshControl = { + let refreshControl = UIRefreshControl() + refreshControl.addTarget(self, action: #selector(refreshData(_:)), for: .valueChanged) + + return refreshControl + }() + // MARK: - UIViewController Lifecycle override func viewDidLoad() { super.viewDidLoad() tableView.registerNib(for: LessonTableViewCell.self) + + if #available(iOS 10.0, *) { + tableView.refreshControl = topicsRefreshControl + } else { + tableView.addSubview(topicsRefreshControl) + } + presenter.refresh() } @@ -46,6 +61,13 @@ final class LessonsTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) + presenter.selectLesson(with: lessons[indexPath.row]) + } + + // MARK: - Private API + + @objc private func refreshData(_ sender: Any) { + presenter.refresh() } } From 9168ced406bc7628fc209e51f6febc6a7d855f11 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 26 Jul 2018 13:00:52 +0300 Subject: [PATCH 06/14] Create CourseService --- .../AppDelegate/AppDelegate.swift | 1 + .../RootNavigationManager.swift | 3 +- .../ServiceComponents.swift | 1 + .../ServiceComponentsAssembly.swift | 7 +++++ .../CourseService/CourseService.swift | 25 +++++++++++++++++ .../CourseService/CourseServiceImpl.swift | 28 +++++++++++++++++++ Stepic.xcodeproj/project.pbxproj | 16 +++++++++++ 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseService.swift create mode 100644 ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseServiceImpl.swift diff --git a/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift b/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift index aafff31620..ef819624a9 100644 --- a/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift +++ b/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift @@ -27,6 +27,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { authAPI: AuthAPI(), stepicsAPI: StepicsAPI(), profilesAPI: ProfilesAPI(), + coursesAPI: CoursesAPI(), defaultsStorageManager: DefaultsStorageManager(), randomCredentialsGenerator: RandomCredentialsGeneratorImplementation() ) diff --git a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift index 36a38f643d..56bd6c28b0 100644 --- a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift +++ b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift @@ -51,7 +51,8 @@ extension RootNavigationManager: TopicsRouter { router: self, topicId: id, knowledgeGraph: knowledgeGraph, - lessonsService: serviceComponents.lessonsService + lessonsService: serviceComponents.lessonsService, + courseService: serviceComponents.courseService, ) controller.presenter = presenter controller.title = knowledgeGraph[id]?.key.title diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift index bbfa769249..af38c53216 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift @@ -12,4 +12,5 @@ protocol ServiceComponents: class { var userRegistrationService: UserRegistrationService { get } var graphService: GraphService { get } var lessonsService: LessonsService { get } + var courseService: CourseService { get } } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift index 431de41a38..c58a830198 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift @@ -12,18 +12,21 @@ final class ServiceComponentsAssembly: ServiceComponents { private let authAPI: AuthAPI private let stepicsAPI: StepicsAPI private let profilesAPI: ProfilesAPI + private let coursesAPI: CoursesAPI private let defaultsStorageManager: DefaultsStorageManager private let randomCredentialsGenerator: RandomCredentialsGenerator init(authAPI: AuthAPI, stepicsAPI: StepicsAPI, profilesAPI: ProfilesAPI, + coursesAPI: CoursesAPI, defaultsStorageManager: DefaultsStorageManager, randomCredentialsGenerator: RandomCredentialsGenerator ) { self.authAPI = authAPI self.stepicsAPI = stepicsAPI self.profilesAPI = profilesAPI + self.coursesAPI = coursesAPI self.defaultsStorageManager = defaultsStorageManager self.randomCredentialsGenerator = randomCredentialsGenerator } @@ -45,4 +48,8 @@ final class ServiceComponentsAssembly: ServiceComponents { var lessonsService: LessonsService { return LessonsServiceImpl() } + + var courseService: CourseService { + return CourseServiceImpl(coursesAPI: coursesAPI) + } } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseService.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseService.swift new file mode 100644 index 0000000000..70d475ddda --- /dev/null +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseService.swift @@ -0,0 +1,25 @@ +// +// CourseService.swift +// ExamEGERussian +// +// Created by Ivan Magda on 26/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit + +protocol CourseService: class { + + /// Method is used to fetch Course objects from Stepik API + /// + /// - Parameter ids: Ids Course objects + /// - Returns: Promise with a result of an array of Course objects from API. + func fetchCourses(with ids: [Int]) -> Promise<[Course]> + + /// Method is used to obtain Course objects from cache with ids + /// + /// - Parameter ids: Ids Course objects + /// - Returns: Promise with a result of an array of Course objects from cache. + func obtainCourses(with ids: [Int]) -> Promise<[Course]> +} diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseServiceImpl.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseServiceImpl.swift new file mode 100644 index 0000000000..3c27a64587 --- /dev/null +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseServiceImpl.swift @@ -0,0 +1,28 @@ +// +// CourseServiceImpl.swift +// ExamEGERussian +// +// Created by Ivan Magda on 26/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit + +final class CourseServiceImpl: CourseService { + private let coursesAPI: CoursesAPI + + init(coursesAPI: CoursesAPI) { + self.coursesAPI = coursesAPI + } + + func fetchCourses(with ids: [Int]) -> Promise<[Course]> { + return obtainCourses(with: ids).then { courses in + self.coursesAPI.retrieve(ids: ids, existing: courses) + } + } + + func obtainCourses(with ids: [Int]) -> Promise<[Course]> { + return Course.fetchAsync(ids) + } +} diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index 8b353306b4..704df2042e 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -4019,6 +4019,8 @@ 2CCB5A642100A5D60091A605 /* GraphServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB5A632100A5D60091A605 /* GraphServiceMock.swift */; }; 2CCB5A662100B7FA0091A605 /* KnowledgeGraph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB5A652100B7FA0091A605 /* KnowledgeGraph.swift */; }; 2CCB5A682100B8E50091A605 /* KnowledgeGraphVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB5A672100B8E50091A605 /* KnowledgeGraphVertex.swift */; }; + 2CCC59F12109B3A200564D45 /* CourseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC59F02109B3A200564D45 /* CourseService.swift */; }; + 2CCC59F82109B92300564D45 /* CourseServiceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC59F72109B92300564D45 /* CourseServiceImpl.swift */; }; 2CCD5D2F2029F1D4008ACBC7 /* AnalyticsEvents+Adaptive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCEB4941F27755800B45D63 /* AnalyticsEvents+Adaptive.swift */; }; 2CCD5D38202A1898008ACBC7 /* AdaptiveCourseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCD5D36202A1898008ACBC7 /* AdaptiveCourseTableViewCell.swift */; }; 2CCD5D3B202A18A5008ACBC7 /* AdaptiveCourseSelectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCD5D31202A12D5008ACBC7 /* AdaptiveCourseSelectViewController.swift */; }; @@ -5800,6 +5802,8 @@ 2CCB5A632100A5D60091A605 /* GraphServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphServiceMock.swift; sourceTree = ""; }; 2CCB5A652100B7FA0091A605 /* KnowledgeGraph.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnowledgeGraph.swift; sourceTree = ""; }; 2CCB5A672100B8E50091A605 /* KnowledgeGraphVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnowledgeGraphVertex.swift; sourceTree = ""; }; + 2CCC59F02109B3A200564D45 /* CourseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseService.swift; sourceTree = ""; }; + 2CCC59F72109B92300564D45 /* CourseServiceImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseServiceImpl.swift; sourceTree = ""; }; 2CCD5D31202A12D5008ACBC7 /* AdaptiveCourseSelectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCourseSelectViewController.swift; sourceTree = ""; }; 2CCD5D33202A12E3008ACBC7 /* AdaptiveCourseSelectPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCourseSelectPresenter.swift; sourceTree = ""; }; 2CCD5D36202A1898008ACBC7 /* AdaptiveCourseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCourseTableViewCell.swift; sourceTree = ""; }; @@ -8630,6 +8634,7 @@ 2C59F06320EB9FFA00666EE0 /* Services */ = { isa = PBXGroup; children = ( + 2CCC59EF2109B38400564D45 /* CourseService */, 62E9862365BD47119FA0E1D9 /* GraphService */, 2C2FD7822107447300609621 /* LessonsService */, 2C59F06520EBA26C00666EE0 /* UserRegistrationService */, @@ -9333,6 +9338,15 @@ path = AbstractGraphBuilder; sourceTree = ""; }; + 2CCC59EF2109B38400564D45 /* CourseService */ = { + isa = PBXGroup; + children = ( + 2CCC59F02109B3A200564D45 /* CourseService.swift */, + 2CCC59F72109B92300564D45 /* CourseServiceImpl.swift */, + ); + path = CourseService; + sourceTree = ""; + }; 2CCD5D30202A12B3008ACBC7 /* AdaptiveCourseSelect */ = { isa = PBXGroup; children = ( @@ -14889,6 +14903,7 @@ 2C949A3E20ECC9100049E9A8 /* PickerViewController.swift in Sources */, 2CFE247D20ECFC1A00AF1E3D /* Model.xcdatamodeld in Sources */, 2C33CC0620EC3385009956B0 /* ApplicationInfo.swift in Sources */, + 2CCC59F12109B3A200564D45 /* CourseService.swift in Sources */, 2CCB5A452100849A0091A605 /* UIViewController+Alert.swift in Sources */, 2C9499F220ECC0BA0049E9A8 /* DeleteDeviceExecutableTask.swift in Sources */, 2C33CC0D20EC33FF009956B0 /* CreateRequestMaker.swift in Sources */, @@ -14990,6 +15005,7 @@ 2CCB5A462100849A0091A605 /* RandomCredentialsGenerator.swift in Sources */, 2CCB5A6221009E160091A605 /* KnowledgeGraphBuilder.swift in Sources */, 2C949A0320ECC2D70049E9A8 /* NotificationsPresenter.swift in Sources */, + 2CCC59F82109B92300564D45 /* CourseServiceImpl.swift in Sources */, 2C33CC0820EC33CD009956B0 /* User.swift in Sources */, 2C949A5920ECCE800049E9A8 /* SettingsViewController.swift in Sources */, 2C0BD98920ED321B0022C454 /* RootNavigationManager.swift in Sources */, From 706b2f344ea07106244b38bca2c434d1e7512c5f Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 26 Jul 2018 13:01:42 +0300 Subject: [PATCH 07/14] Create EnrollmentService --- .../AppDelegate/AppDelegate.swift | 1 + .../RootNavigationManager.swift | 1 + .../ServiceComponents.swift | 1 + .../ServiceComponentsAssembly.swift | 7 ++++ .../EnrollmentService/EnrollmentService.swift | 18 +++++++++++ .../EnrollmentServiceImpl.swift | 32 +++++++++++++++++++ Stepic.xcodeproj/project.pbxproj | 16 ++++++++++ 7 files changed, 76 insertions(+) create mode 100644 ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentService.swift create mode 100644 ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentServiceImpl.swift diff --git a/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift b/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift index ef819624a9..f04e541187 100644 --- a/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift +++ b/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift @@ -28,6 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { stepicsAPI: StepicsAPI(), profilesAPI: ProfilesAPI(), coursesAPI: CoursesAPI(), + enrollmentsAPI: EnrollmentsAPI(), defaultsStorageManager: DefaultsStorageManager(), randomCredentialsGenerator: RandomCredentialsGeneratorImplementation() ) diff --git a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift index 56bd6c28b0..a79e5bb115 100644 --- a/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift +++ b/ExamEGERussian/Sources/ApplicationLayer/RootNavigationManager/RootNavigationManager.swift @@ -53,6 +53,7 @@ extension RootNavigationManager: TopicsRouter { knowledgeGraph: knowledgeGraph, lessonsService: serviceComponents.lessonsService, courseService: serviceComponents.courseService, + enrollmentService: serviceComponents.enrollmentService ) controller.presenter = presenter controller.title = knowledgeGraph[id]?.key.title diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift index af38c53216..3faf351be5 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponents.swift @@ -13,4 +13,5 @@ protocol ServiceComponents: class { var graphService: GraphService { get } var lessonsService: LessonsService { get } var courseService: CourseService { get } + var enrollmentService: EnrollmentService { get } } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift index c58a830198..13a3161cc1 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift @@ -13,6 +13,7 @@ final class ServiceComponentsAssembly: ServiceComponents { private let stepicsAPI: StepicsAPI private let profilesAPI: ProfilesAPI private let coursesAPI: CoursesAPI + private let enrollmentsAPI: EnrollmentsAPI private let defaultsStorageManager: DefaultsStorageManager private let randomCredentialsGenerator: RandomCredentialsGenerator @@ -20,6 +21,7 @@ final class ServiceComponentsAssembly: ServiceComponents { stepicsAPI: StepicsAPI, profilesAPI: ProfilesAPI, coursesAPI: CoursesAPI, + enrollmentsAPI: EnrollmentsAPI, defaultsStorageManager: DefaultsStorageManager, randomCredentialsGenerator: RandomCredentialsGenerator ) { @@ -27,6 +29,7 @@ final class ServiceComponentsAssembly: ServiceComponents { self.stepicsAPI = stepicsAPI self.profilesAPI = profilesAPI self.coursesAPI = coursesAPI + self.enrollmentsAPI = enrollmentsAPI self.defaultsStorageManager = defaultsStorageManager self.randomCredentialsGenerator = randomCredentialsGenerator } @@ -52,4 +55,8 @@ final class ServiceComponentsAssembly: ServiceComponents { var courseService: CourseService { return CourseServiceImpl(coursesAPI: coursesAPI) } + + var enrollmentService: EnrollmentService { + return EnrollmentServiceImpl(enrollmentsAPI: enrollmentsAPI) + } } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentService.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentService.swift new file mode 100644 index 0000000000..d60a37b9e9 --- /dev/null +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentService.swift @@ -0,0 +1,18 @@ +// +// EnrollmentService.swift +// ExamEGERussian +// +// Created by Ivan Magda on 26/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit + +protocol EnrollmentService: class { + /// Method is used to joining Course object with specified id. + /// + /// - Parameter course: Course to join. + /// - Returns: Promise when fullfilled. + func joinCourse(_ course: Course) -> Promise +} diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentServiceImpl.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentServiceImpl.swift new file mode 100644 index 0000000000..fecfa1e401 --- /dev/null +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentServiceImpl.swift @@ -0,0 +1,32 @@ +// +// EnrollmentServiceImpl.swift +// ExamEGERussian +// +// Created by Ivan Magda on 26/07/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit + +final class EnrollmentServiceImpl: EnrollmentService { + private enum EnrollmentServiceError: Error { + case joinCourseFailed + } + + private let enrollmentsAPI: EnrollmentsAPI + + init(enrollmentsAPI: EnrollmentsAPI) { + self.enrollmentsAPI = enrollmentsAPI + } + + func joinCourse(_ course: Course) -> Promise { + return Promise { seal in + self.enrollmentsAPI.joinCourse(course).done { + seal.fulfill(course) + }.catch { _ in + seal.reject(EnrollmentServiceError.joinCourseFailed) + } + } + } +} diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index 704df2042e..21249fc261 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -4021,6 +4021,8 @@ 2CCB5A682100B8E50091A605 /* KnowledgeGraphVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCB5A672100B8E50091A605 /* KnowledgeGraphVertex.swift */; }; 2CCC59F12109B3A200564D45 /* CourseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC59F02109B3A200564D45 /* CourseService.swift */; }; 2CCC59F82109B92300564D45 /* CourseServiceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC59F72109B92300564D45 /* CourseServiceImpl.swift */; }; + 2CCC59FC2109BB4800564D45 /* EnrollmentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC59FB2109BB4800564D45 /* EnrollmentService.swift */; }; + 2CCC59FE2109BB9800564D45 /* EnrollmentServiceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC59FD2109BB9800564D45 /* EnrollmentServiceImpl.swift */; }; 2CCD5D2F2029F1D4008ACBC7 /* AnalyticsEvents+Adaptive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCEB4941F27755800B45D63 /* AnalyticsEvents+Adaptive.swift */; }; 2CCD5D38202A1898008ACBC7 /* AdaptiveCourseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCD5D36202A1898008ACBC7 /* AdaptiveCourseTableViewCell.swift */; }; 2CCD5D3B202A18A5008ACBC7 /* AdaptiveCourseSelectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCD5D31202A12D5008ACBC7 /* AdaptiveCourseSelectViewController.swift */; }; @@ -5804,6 +5806,8 @@ 2CCB5A672100B8E50091A605 /* KnowledgeGraphVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KnowledgeGraphVertex.swift; sourceTree = ""; }; 2CCC59F02109B3A200564D45 /* CourseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseService.swift; sourceTree = ""; }; 2CCC59F72109B92300564D45 /* CourseServiceImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseServiceImpl.swift; sourceTree = ""; }; + 2CCC59FB2109BB4800564D45 /* EnrollmentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnrollmentService.swift; sourceTree = ""; }; + 2CCC59FD2109BB9800564D45 /* EnrollmentServiceImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnrollmentServiceImpl.swift; sourceTree = ""; }; 2CCD5D31202A12D5008ACBC7 /* AdaptiveCourseSelectViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCourseSelectViewController.swift; sourceTree = ""; }; 2CCD5D33202A12E3008ACBC7 /* AdaptiveCourseSelectPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCourseSelectPresenter.swift; sourceTree = ""; }; 2CCD5D36202A1898008ACBC7 /* AdaptiveCourseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCourseTableViewCell.swift; sourceTree = ""; }; @@ -8635,6 +8639,7 @@ isa = PBXGroup; children = ( 2CCC59EF2109B38400564D45 /* CourseService */, + 2CCC59FA2109BB3A00564D45 /* EnrollmentService */, 62E9862365BD47119FA0E1D9 /* GraphService */, 2C2FD7822107447300609621 /* LessonsService */, 2C59F06520EBA26C00666EE0 /* UserRegistrationService */, @@ -9347,6 +9352,15 @@ path = CourseService; sourceTree = ""; }; + 2CCC59FA2109BB3A00564D45 /* EnrollmentService */ = { + isa = PBXGroup; + children = ( + 2CCC59FB2109BB4800564D45 /* EnrollmentService.swift */, + 2CCC59FD2109BB9800564D45 /* EnrollmentServiceImpl.swift */, + ); + path = EnrollmentService; + sourceTree = ""; + }; 2CCD5D30202A12B3008ACBC7 /* AdaptiveCourseSelect */ = { isa = PBXGroup; children = ( @@ -15101,6 +15115,7 @@ 2C949A1720ECC5480049E9A8 /* ProfileViewController.swift in Sources */, 2CCB5A662100B7FA0091A605 /* KnowledgeGraph.swift in Sources */, 2C33CBDB20EC2C2E009956B0 /* UserRegistrationServiceImplementation.swift in Sources */, + 2CCC59FE2109BB9800564D45 /* EnrollmentServiceImpl.swift in Sources */, 2C949A0820ECC3B80049E9A8 /* NotificationDataExtractor.swift in Sources */, 2C949A1220ECC4930049E9A8 /* NotificationRequestAlertManager.swift in Sources */, 2C949A0620ECC3130049E9A8 /* AlertManager.swift in Sources */, @@ -15148,6 +15163,7 @@ 2C9499C220ECBBAF0049E9A8 /* CourseMetainfoEntity.swift in Sources */, 2C949A1D20ECC58A0049E9A8 /* MenuUIManager.swift in Sources */, 2C949A1C20ECC5690049E9A8 /* NibInitializableView.swift in Sources */, + 2CCC59FC2109BB4800564D45 /* EnrollmentService.swift in Sources */, 2C949A2320ECC5D60049E9A8 /* ProfileDescriptionPresenter.swift in Sources */, 2C9499E820ECBF9B0049E9A8 /* NotificationRequestAlertContext.swift in Sources */, 2C949A2920ECC62E0049E9A8 /* ProfileAchievementsContentView.swift in Sources */, From 1925f2636a2f19e2e39e48df38bc648d0dd94c0d Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 26 Jul 2018 13:02:24 +0300 Subject: [PATCH 08/14] Join courses --- .../Presenter/LessonsPresenterImpl.swift | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift index 294edffaba..19c505828d 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift @@ -17,27 +17,40 @@ final class LessonsPresenterImpl: LessonsPresenter { private weak var view: LessonsView? private weak var router: LessonsRouter? + private let topicId: String private let knowledgeGraph: KnowledgeGraph private var lessons = [LessonPlainObject]() + private let lessonsService: LessonsService + private let courseService: CourseService + private let enrollmentService: EnrollmentService + private var topic: KnowledgeGraphVertex { + return knowledgeGraph[topicId]!.key + } private var lessonsIds: [Int] { - guard let topic = knowledgeGraph[topicId]?.key else { return [] } return topic.lessons.filter { $0.type == .theory }.map { $0.id } } + private var coursesIds: [Int] { + return topic.lessons.compactMap { Int($0.courseId) } + } - init(view: LessonsView, router: LessonsRouter, topicId: String, knowledgeGraph: KnowledgeGraph, - lessonsService: LessonsService) { + init(view: LessonsView, router: LessonsRouter, topicId: String, + knowledgeGraph: KnowledgeGraph, lessonsService: LessonsService, + courseService: CourseService, enrollmentService: EnrollmentService) { self.view = view self.router = router self.topicId = topicId self.knowledgeGraph = knowledgeGraph self.lessonsService = lessonsService + self.courseService = courseService + self.enrollmentService = enrollmentService } func refresh() { fetchLessons() + joinCoursesIfNeeded() } func selectLesson(with viewData: LessonsViewData) { @@ -46,6 +59,29 @@ final class LessonsPresenterImpl: LessonsPresenter { // MARK: - Private API + private func joinCoursesIfNeeded() { + guard !coursesIds.isEmpty else { return } + courseService.obtainCourses(with: coursesIds).then { courses -> Promise<[Int]> in + var ids = Set(self.coursesIds) + courses.filter { $0.enrolled }.map { $0.id }.forEach { ids.remove($0) } + return .value(Array(ids)) + }.then { ids -> Promise<[Course]> in + guard !ids.isEmpty else { return .value([]) } + return self.courseService.fetchCourses(with: ids) + }.then { courses in + when(fulfilled: courses.map { self.joinCourse($0) }) + }.done { courses in + print("Successfully joined courses with ids: \(courses.map { $0.id })") + }.catch { [weak self] error in + self?.displayError(error) + } + } + + private func joinCourse(_ course: Course) -> Promise { + guard !course.enrolled else { return .value(course) } + return enrollmentService.joinCourse(course) + } + private func fetchLessons() { guard lessonsIds.count > 0 else { return } lessonsService.obtainLessons(with: lessonsIds).done { [weak self] responseModel in From 0a112d5496e1d86ff4a3ac4942780e5eac604a99 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 26 Jul 2018 13:05:53 +0300 Subject: [PATCH 09/14] Refactor rename GraphService's obtainGraph() to fetchGraph() --- .../Services/GraphService/GraphService.swift | 5 ++++- .../Services/GraphService/GraphServiceImpl.swift | 2 +- .../Topics/Presenter/TopicsPresenterImpl.swift | 2 +- ExamEGERussianTests/Helpers/Mocks/GraphServiceMock.swift | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/GraphService/GraphService.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/GraphService/GraphService.swift index 8773b09d6c..28d8d92875 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Services/GraphService/GraphService.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/GraphService/GraphService.swift @@ -10,5 +10,8 @@ import Foundation import PromiseKit protocol GraphService: class { - func obtainGraph() -> Promise + /// Method is used to fetch KnowledgeGraphPlainObject object from API. + /// + /// - Returns: Promise with a result of KnowledgeGraphPlainObject. + func fetchGraph() -> Promise } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/GraphService/GraphServiceImpl.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/GraphService/GraphServiceImpl.swift index e4a3c5494a..9abb89c77c 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Services/GraphService/GraphServiceImpl.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/GraphService/GraphServiceImpl.swift @@ -13,7 +13,7 @@ import Alamofire final class GraphServiceImpl: GraphService { private static let url = URL(string: "https://www.dropbox.com/s/l8n1wny8qu0gbqt/example.json?dl=1")! - func obtainGraph() -> Promise { + func fetchGraph() -> Promise { return Alamofire .request(GraphServiceImpl.url) .responseDecodable(KnowledgeGraphPlainObject.self) diff --git a/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift index 8d7f0bfb8d..06161beeef 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Topics/Presenter/TopicsPresenterImpl.swift @@ -52,7 +52,7 @@ final class TopicsPresenterImpl: TopicsPresenter { } private func fetchGraphData() { - graphService.obtainGraph().done { [weak self] responseModel in + graphService.fetchGraph().done { [weak self] responseModel in guard let `self` = self else { return } let builder = KnowledgeGraphBuilder(graphPlainObject: responseModel) guard let graph = builder.build() as? KnowledgeGraph else { return } diff --git a/ExamEGERussianTests/Helpers/Mocks/GraphServiceMock.swift b/ExamEGERussianTests/Helpers/Mocks/GraphServiceMock.swift index 245fc9f0b8..93183a72e9 100644 --- a/ExamEGERussianTests/Helpers/Mocks/GraphServiceMock.swift +++ b/ExamEGERussianTests/Helpers/Mocks/GraphServiceMock.swift @@ -17,7 +17,7 @@ final class GraphServiceMock: GraphService { var resultToBeReturned: Promise = Promise(error: Error.mockError) - func obtainGraph() -> Promise { + func fetchGraph() -> Promise { return resultToBeReturned } } From 96e81167d347c5cbe8bebc47b623df03f6bc1b4b Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 26 Jul 2018 13:09:48 +0300 Subject: [PATCH 10/14] Refactor rename LessonsService's obtainLessons() to fetchLessons() --- .../Services/LessonsService/LessonsService.swift | 6 +++++- .../Services/LessonsService/LessonsServiceImpl.swift | 2 +- .../Lessons/Presenter/LessonsPresenterImpl.swift | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift index 4b1ea21a1a..7b9e6980f9 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift @@ -10,5 +10,9 @@ import Foundation import PromiseKit protocol LessonsService: class { - func obtainLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> + /// Method is used to fetch lessons from Stepik API. + /// + /// - Parameter ids: Lessons ids. + /// - Returns: Promise with an array of LessonPlainObjects. + func fetchLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift index 8980cbd4e6..d82128727b 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift @@ -15,7 +15,7 @@ final class LessonsServiceImpl: LessonsService { let lessons: [LessonPlainObject] } - func obtainLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> { + func fetchLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> { let url = "\(StepicApplicationsInfo.apiURL)/lessons" let parameters = ["ids": ids] let headers = AuthInfo.shared.initialHTTPHeaders diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift index 19c505828d..efd3fbdaed 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift @@ -84,7 +84,7 @@ final class LessonsPresenterImpl: LessonsPresenter { private func fetchLessons() { guard lessonsIds.count > 0 else { return } - lessonsService.obtainLessons(with: lessonsIds).done { [weak self] responseModel in + lessonsService.fetchLessons(with: lessonsIds).done { [weak self] responseModel in guard let `self` = self else { return } self.lessons = responseModel let viewData = self.viewLessons(from: self.lessons) From 7e0759fa26ccb17ab4933023b7080f30db5227e9 Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 26 Jul 2018 14:09:13 +0300 Subject: [PATCH 11/14] Support cache in LessonsService --- .../AppDelegate/AppDelegate.swift | 1 + .../ServiceComponentsAssembly.swift | 5 +- .../CourseService/CourseService.swift | 2 - .../LessonsService/LessonsService.swift | 5 ++ .../LessonsService/LessonsServiceImpl.swift | 51 ++++++++++++++----- .../Presenter/LessonsPresenterImpl.swift | 27 +++++++--- 6 files changed, 68 insertions(+), 23 deletions(-) diff --git a/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift b/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift index f04e541187..59d6b2349c 100644 --- a/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift +++ b/ExamEGERussian/Sources/ApplicationLayer/AppDelegate/AppDelegate.swift @@ -29,6 +29,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { profilesAPI: ProfilesAPI(), coursesAPI: CoursesAPI(), enrollmentsAPI: EnrollmentsAPI(), + lessonsAPI: LessonsAPI(), defaultsStorageManager: DefaultsStorageManager(), randomCredentialsGenerator: RandomCredentialsGeneratorImplementation() ) diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift index 13a3161cc1..d0f1244314 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Assemblies/ServiceComponentsAssembly/ServiceComponentsAssembly.swift @@ -14,6 +14,7 @@ final class ServiceComponentsAssembly: ServiceComponents { private let profilesAPI: ProfilesAPI private let coursesAPI: CoursesAPI private let enrollmentsAPI: EnrollmentsAPI + private let lessonsAPI: LessonsAPI private let defaultsStorageManager: DefaultsStorageManager private let randomCredentialsGenerator: RandomCredentialsGenerator @@ -22,6 +23,7 @@ final class ServiceComponentsAssembly: ServiceComponents { profilesAPI: ProfilesAPI, coursesAPI: CoursesAPI, enrollmentsAPI: EnrollmentsAPI, + lessonsAPI: LessonsAPI, defaultsStorageManager: DefaultsStorageManager, randomCredentialsGenerator: RandomCredentialsGenerator ) { @@ -30,6 +32,7 @@ final class ServiceComponentsAssembly: ServiceComponents { self.profilesAPI = profilesAPI self.coursesAPI = coursesAPI self.enrollmentsAPI = enrollmentsAPI + self.lessonsAPI = lessonsAPI self.defaultsStorageManager = defaultsStorageManager self.randomCredentialsGenerator = randomCredentialsGenerator } @@ -49,7 +52,7 @@ final class ServiceComponentsAssembly: ServiceComponents { } var lessonsService: LessonsService { - return LessonsServiceImpl() + return LessonsServiceImpl(lessonsAPI: lessonsAPI) } var courseService: CourseService { diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseService.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseService.swift index 70d475ddda..1385c60ee8 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseService.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/CourseService/CourseService.swift @@ -10,13 +10,11 @@ import Foundation import PromiseKit protocol CourseService: class { - /// Method is used to fetch Course objects from Stepik API /// /// - Parameter ids: Ids Course objects /// - Returns: Promise with a result of an array of Course objects from API. func fetchCourses(with ids: [Int]) -> Promise<[Course]> - /// Method is used to obtain Course objects from cache with ids /// /// - Parameter ids: Ids Course objects diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift index 7b9e6980f9..24d4926772 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsService.swift @@ -15,4 +15,9 @@ protocol LessonsService: class { /// - Parameter ids: Lessons ids. /// - Returns: Promise with an array of LessonPlainObjects. func fetchLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> + /// Method is used to obtain lessons from cache. + /// + /// - Parameter ids: Lessons ids. + /// - Returns: Promise with an array of cached LessonPlainObjects. + func obtainLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> } diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift index d82128727b..7d4b096ca7 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/LessonsService/LessonsServiceImpl.swift @@ -7,25 +7,50 @@ // import Foundation +import CoreData import PromiseKit -import Alamofire final class LessonsServiceImpl: LessonsService { - private struct Response: Codable { - let lessons: [LessonPlainObject] + private let lessonsAPI: LessonsAPI + + init(lessonsAPI: LessonsAPI) { + self.lessonsAPI = lessonsAPI } func fetchLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> { - let url = "\(StepicApplicationsInfo.apiURL)/lessons" - let parameters = ["ids": ids] - let headers = AuthInfo.shared.initialHTTPHeaders - - return firstly { - Alamofire - .request(url, parameters: parameters, headers: headers) - .responseDecodable(Response.self) - }.then { response in - Promise.value(response.lessons) + return executeFetchRequest(ids: ids) + .then { self.lessonsAPI.retrieve(ids: ids, existing: $0) } + .mapValues { self.toPlainObject($0) } + } + + func obtainLessons(with ids: [Int]) -> Promise<[LessonPlainObject]> { + return executeFetchRequest(ids: ids).mapValues { self.toPlainObject($0) } + } + + // MARK: - Private API + + private func executeFetchRequest(ids: [Int]) -> Promise<[Lesson]> { + let request = NSFetchRequest(entityName: String(describing: Lesson.self)) + let descriptor = NSSortDescriptor(key: "managedId", ascending: true) + + let idPredicates = ids.map { NSPredicate(format: "managedId == %@", $0 as NSNumber) } + let predicate = NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.or, subpredicates: idPredicates) + request.predicate = predicate + request.sortDescriptors = [descriptor] + + return Promise<[Lesson]> { seal in + let asyncRequest = NSAsynchronousFetchRequest(fetchRequest: request, completionBlock: { results in + guard let lessons = results.finalResult as? [Lesson] else { + seal.fulfill([]) + return + } + seal.fulfill(lessons) + }) + _ = try? CoreDataHelper.instance.context.execute(asyncRequest) } } + + private func toPlainObject(_ lesson: Lesson) -> LessonPlainObject { + return LessonPlainObject(id: lesson.id, steps: lesson.steps.map { $0.id }, title: lesson.title) + } } diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift index efd3fbdaed..dda5b4b54f 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift @@ -20,7 +20,11 @@ final class LessonsPresenterImpl: LessonsPresenter { private let topicId: String private let knowledgeGraph: KnowledgeGraph - private var lessons = [LessonPlainObject]() + private var lessons = [LessonPlainObject]() { + didSet { + self.view?.setLessons(viewLessons(from: lessons)) + } + } private let lessonsService: LessonsService private let courseService: CourseService @@ -49,7 +53,11 @@ final class LessonsPresenterImpl: LessonsPresenter { } func refresh() { - fetchLessons() + if lessons.isEmpty { + obtainLessonsFromCache() + } else { + fetchLessons() + } joinCoursesIfNeeded() } @@ -82,13 +90,18 @@ final class LessonsPresenterImpl: LessonsPresenter { return enrollmentService.joinCourse(course) } + private func obtainLessonsFromCache() { + lessonsService.obtainLessons(with: lessonsIds).done { [weak self] lessons in + self?.lessons = lessons + }.catch { [weak self] error in + self?.displayError(error) + } + } + private func fetchLessons() { guard lessonsIds.count > 0 else { return } - lessonsService.fetchLessons(with: lessonsIds).done { [weak self] responseModel in - guard let `self` = self else { return } - self.lessons = responseModel - let viewData = self.viewLessons(from: self.lessons) - self.view?.setLessons(viewData) + lessonsService.fetchLessons(with: lessonsIds).done { [weak self] lessons in + self?.lessons = lessons }.catch { [weak self] error in self?.displayError(error) } From febb2838c6beecfc900f4ddd0be97b02b50afa4e Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Thu, 26 Jul 2018 14:11:27 +0300 Subject: [PATCH 12/14] Fix tests --- .../ServiceComponentsAssemblyTestsHelper.swift | 5 +++-- .../TopicsModuleTests/TopicsPresenterTests.swift | 2 +- .../TopicsModuleTests/TopicsTableViewControllerTests.swift | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ExamEGERussianTests/AssembliesTests/ServiceComponentsAssemblyTests/ServiceComponentsAssemblyTestsHelper.swift b/ExamEGERussianTests/AssembliesTests/ServiceComponentsAssemblyTests/ServiceComponentsAssemblyTestsHelper.swift index 64a847eed3..dc389dd8e5 100644 --- a/ExamEGERussianTests/AssembliesTests/ServiceComponentsAssemblyTests/ServiceComponentsAssemblyTestsHelper.swift +++ b/ExamEGERussianTests/AssembliesTests/ServiceComponentsAssemblyTests/ServiceComponentsAssemblyTestsHelper.swift @@ -10,7 +10,6 @@ import Foundation @testable import ExamEGERussian final class ServiceComponentsAssemblyTestsHelper { - let serviceComponents: ServiceComponents init() { @@ -18,9 +17,11 @@ final class ServiceComponentsAssemblyTestsHelper { authAPI: AuthAPI(), stepicsAPI: StepicsAPI(), profilesAPI: ProfilesAPI(), + coursesAPI: CoursesAPI(), + enrollmentsAPI: EnrollmentsAPI(), + lessonsAPI: LessonsAPI(), defaultsStorageManager: DefaultsStorageManager(), randomCredentialsGenerator: RandomCredentialsGeneratorImplementation() ) } - } diff --git a/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsPresenterTests.swift b/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsPresenterTests.swift index fc1087576a..83a92cfbdd 100644 --- a/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsPresenterTests.swift +++ b/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsPresenterTests.swift @@ -22,7 +22,7 @@ class TopicsPresenterTests: XCTestCase { topicsPresenter = TopicsPresenterImpl( view: topicsViewSpy, - model: KnowledgeGraph(), + knowledgeGraph: KnowledgeGraph(), router: TopicsRouterMock(), userRegistrationService: userRegistrationService, graphService: graphService diff --git a/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsTableViewControllerTests.swift b/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsTableViewControllerTests.swift index e92053f14e..c0e7641faf 100644 --- a/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsTableViewControllerTests.swift +++ b/ExamEGERussianTests/PresentationTests/TopicsModuleTests/TopicsTableViewControllerTests.swift @@ -15,7 +15,7 @@ class TopicsTableViewControllerTests: XCTestCase { let vc = TopicsTableViewController() let presenter = TopicsPresenterImpl( view: vc, - model: KnowledgeGraph(), + knowledgeGraph: KnowledgeGraph(), router: TopicsRouterMock(), userRegistrationService: UserRegistrationServiceMock(), graphService: GraphServiceMock() From 711f19bf6bc15392fc6f04e788a5e1e3b733c56d Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 30 Jul 2018 12:32:54 +0300 Subject: [PATCH 13/14] EnrollmentServiceImpl simplify call to joinCourse(:) --- .../EnrollmentService/EnrollmentServiceImpl.swift | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentServiceImpl.swift b/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentServiceImpl.swift index fecfa1e401..c86d543330 100644 --- a/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentServiceImpl.swift +++ b/ExamEGERussian/Sources/BusinessLogicLayer/Services/EnrollmentService/EnrollmentServiceImpl.swift @@ -10,10 +10,6 @@ import Foundation import PromiseKit final class EnrollmentServiceImpl: EnrollmentService { - private enum EnrollmentServiceError: Error { - case joinCourseFailed - } - private let enrollmentsAPI: EnrollmentsAPI init(enrollmentsAPI: EnrollmentsAPI) { @@ -21,12 +17,8 @@ final class EnrollmentServiceImpl: EnrollmentService { } func joinCourse(_ course: Course) -> Promise { - return Promise { seal in - self.enrollmentsAPI.joinCourse(course).done { - seal.fulfill(course) - }.catch { _ in - seal.reject(EnrollmentServiceError.joinCourseFailed) - } + return enrollmentsAPI.joinCourse(course).then { _ -> Promise in + .value(course) } } } From 07a7c1f33ae30ef21795a23aa6c156728306800d Mon Sep 17 00:00:00 2001 From: Ivan Magda Date: Mon, 30 Jul 2018 12:34:07 +0300 Subject: [PATCH 14/14] Update guard statements style --- .../Presenter/LessonsPresenterImpl.swift | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift index dda5b4b54f..ffc2384908 100644 --- a/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift +++ b/ExamEGERussian/Sources/PresentationLayer/Lessons/Presenter/LessonsPresenterImpl.swift @@ -68,13 +68,20 @@ final class LessonsPresenterImpl: LessonsPresenter { // MARK: - Private API private func joinCoursesIfNeeded() { - guard !coursesIds.isEmpty else { return } + guard !coursesIds.isEmpty else { + return + } + courseService.obtainCourses(with: coursesIds).then { courses -> Promise<[Int]> in var ids = Set(self.coursesIds) courses.filter { $0.enrolled }.map { $0.id }.forEach { ids.remove($0) } + return .value(Array(ids)) }.then { ids -> Promise<[Course]> in - guard !ids.isEmpty else { return .value([]) } + guard !ids.isEmpty else { + return .value([]) + } + return self.courseService.fetchCourses(with: ids) }.then { courses in when(fulfilled: courses.map { self.joinCourse($0) }) @@ -86,7 +93,10 @@ final class LessonsPresenterImpl: LessonsPresenter { } private func joinCourse(_ course: Course) -> Promise { - guard !course.enrolled else { return .value(course) } + guard !course.enrolled else { + return .value(course) + } + return enrollmentService.joinCourse(course) } @@ -99,7 +109,10 @@ final class LessonsPresenterImpl: LessonsPresenter { } private func fetchLessons() { - guard lessonsIds.count > 0 else { return } + guard lessonsIds.count > 0 else { + return + } + lessonsService.fetchLessons(with: lessonsIds).done { [weak self] lessons in self?.lessons = lessons }.catch { [weak self] error in