diff --git a/Podfile b/Podfile index c3d4491e54..c6ef6bdcf4 100644 --- a/Podfile +++ b/Podfile @@ -13,7 +13,7 @@ project 'Stepic', 'Develop Release' => :release def shared_pods - pod 'Alamofire', '5.4.2' + pod 'Alamofire', '5.4.3' pod 'Atributika', '4.9.10' pod 'SwiftyJSON', '5.0.0' pod 'SDWebImage', '5.11.0' @@ -45,13 +45,13 @@ def all_pods pod 'SnapKit', '5.0.1' # Firebase - pod 'Firebase/Core', '7.10.0' - pod 'Firebase/Messaging', '7.10.0' - pod 'Firebase/Analytics', '7.10.0' - pod 'Firebase/Crashlytics', '7.10.0' - pod 'Firebase/RemoteConfig', '7.10.0' + pod 'Firebase/Core', '7.11.0' + pod 'Firebase/Messaging', '7.11.0' + pod 'Firebase/Analytics', '7.11.0' + pod 'Firebase/Crashlytics', '7.11.0' + pod 'Firebase/RemoteConfig', '7.11.0' - pod 'YandexMobileMetrica/Dynamic', '3.15.0' + pod 'YandexMobileMetrica/Dynamic', '3.15.1' pod 'Amplitude', '8.2.1' pod 'Branch', '1.39.2' @@ -72,7 +72,7 @@ def all_pods pod 'PanModal', :git => 'https://github.com/ivan-magda/PanModal.git', :branch => 'remove-presenting-appearance-transitions' pod 'Agrume', '5.6.13' - pod 'Highlightr', '2.1.0' + pod 'Highlightr', :git => 'https://github.com/raspu/Highlightr.git', :tag => '2.1.2' pod 'TTTAttributedLabel', '2.0.0' pod 'lottie-ios', '3.2.1' pod 'Koloda', '5.0.1' diff --git a/Podfile.lock b/Podfile.lock index 4e7a2296c3..4af89be8fc 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -2,7 +2,7 @@ PODS: - ActionSheetPicker-3.0 (2.7.1) - Agrume (5.6.13): - SwiftyGif - - Alamofire (5.4.2) + - Alamofire (5.4.3) - Amplitude (8.2.1) - AppAuth (1.4.0): - AppAuth/Core (= 1.4.0) @@ -31,73 +31,92 @@ PODS: - FBSDKLoginKit/Login (= 8.2.0) - FBSDKLoginKit/Login (8.2.0): - FBSDKCoreKit (~> 8.2.0) - - Firebase/Analytics (7.10.0): + - Firebase/Analytics (7.11.0): - Firebase/Core - - Firebase/Core (7.10.0): + - Firebase/Core (7.11.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 7.10.0) - - Firebase/CoreOnly (7.10.0): - - FirebaseCore (= 7.10.0) - - Firebase/Crashlytics (7.10.0): + - FirebaseAnalytics (~> 7.11.0) + - Firebase/CoreOnly (7.11.0): + - FirebaseCore (= 7.11.0) + - Firebase/Crashlytics (7.11.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 7.10.0) - - Firebase/Messaging (7.10.0): + - FirebaseCrashlytics (~> 7.11.0) + - Firebase/Messaging (7.11.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 7.10.0) - - Firebase/RemoteConfig (7.10.0): + - FirebaseMessaging (~> 7.11.0) + - Firebase/RemoteConfig (7.11.0): - Firebase/CoreOnly - - FirebaseRemoteConfig (~> 7.10.0) - - FirebaseABTesting (7.10.0): + - FirebaseRemoteConfig (~> 7.11.0) + - FirebaseABTesting (7.11.0): - FirebaseCore (~> 7.0) - - FirebaseAnalytics (7.10.0): + - FirebaseAnalytics (7.11.0): + - FirebaseAnalytics/AdIdSupport (= 7.11.0) - FirebaseCore (~> 7.0) - FirebaseInstallations (~> 7.0) - - GoogleAppMeasurement (= 7.10.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - GoogleUtilities/MethodSwizzler (~> 7.0) - GoogleUtilities/Network (~> 7.0) - "GoogleUtilities/NSData+zlib (~> 7.0)" - nanopb (~> 2.30908.0) - - FirebaseCore (7.10.0): + - FirebaseAnalytics/AdIdSupport (7.11.0): + - FirebaseAnalytics/Base (= 7.11.0) + - FirebaseCore (~> 7.0) + - FirebaseInstallations (~> 7.0) + - GoogleAppMeasurement/AdIdSupport (= 7.11.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.0) + - GoogleUtilities/MethodSwizzler (~> 7.0) + - GoogleUtilities/Network (~> 7.0) + - "GoogleUtilities/NSData+zlib (~> 7.0)" + - nanopb (~> 2.30908.0) + - FirebaseAnalytics/Base (7.11.0): + - FirebaseCore (~> 7.0) + - FirebaseInstallations (~> 7.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.0) + - GoogleUtilities/MethodSwizzler (~> 7.0) + - GoogleUtilities/Network (~> 7.0) + - "GoogleUtilities/NSData+zlib (~> 7.0)" + - nanopb (~> 2.30908.0) + - FirebaseCore (7.11.0): - FirebaseCoreDiagnostics (~> 7.4) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/Logger (~> 7.0) - - FirebaseCoreDiagnostics (7.10.0): + - FirebaseCoreDiagnostics (7.11.0): - GoogleDataTransport (~> 8.4) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/Logger (~> 7.0) - nanopb (~> 2.30908.0) - - FirebaseCrashlytics (7.10.0): + - FirebaseCrashlytics (7.11.0): - FirebaseCore (~> 7.0) - FirebaseInstallations (~> 7.0) - GoogleDataTransport (~> 8.4) - nanopb (~> 2.30908.0) - PromisesObjC (~> 1.2) - - FirebaseInstallations (7.10.0): + - FirebaseInstallations (7.11.0): - FirebaseCore (~> 7.0) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/UserDefaults (~> 7.0) - PromisesObjC (~> 1.2) - - FirebaseInstanceID (7.10.0): + - FirebaseInstanceID (7.11.0): - FirebaseCore (~> 7.0) - FirebaseInstallations (~> 7.0) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/UserDefaults (~> 7.0) - - FirebaseMessaging (7.10.0): + - FirebaseMessaging (7.11.0): - FirebaseCore (~> 7.0) + - FirebaseInstallations (~> 7.0) - FirebaseInstanceID (~> 7.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - GoogleUtilities/Environment (~> 7.0) - GoogleUtilities/Reachability (~> 7.0) - GoogleUtilities/UserDefaults (~> 7.0) - - FirebaseRemoteConfig (7.10.0): + - FirebaseRemoteConfig (7.11.0): - FirebaseABTesting (~> 7.0) - FirebaseCore (~> 7.0) - FirebaseInstallations (~> 7.0) - GoogleUtilities/Environment (~> 7.0) - "GoogleUtilities/NSData+zlib (~> 7.0)" - FLEX (4.4.1) - - GoogleAppMeasurement (7.10.0): + - GoogleAppMeasurement/AdIdSupport (7.11.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.0) - GoogleUtilities/MethodSwizzler (~> 7.0) - GoogleUtilities/Network (~> 7.0) @@ -190,17 +209,17 @@ PODS: - TUSafariActivity (1.0.4) - URITemplate (3.0.0) - VK-ios-sdk (1.6.2) - - YandexMobileMetrica/Dynamic (3.15.0): - - YandexMobileMetrica/Dynamic/Core (= 3.15.0) - - YandexMobileMetrica/Dynamic/Crashes (= 3.15.0) - - YandexMobileMetrica/Dynamic/Core (3.15.0) - - YandexMobileMetrica/Dynamic/Crashes (3.15.0): + - YandexMobileMetrica/Dynamic (3.15.1): + - YandexMobileMetrica/Dynamic/Core (= 3.15.1) + - YandexMobileMetrica/Dynamic/Crashes (= 3.15.1) + - YandexMobileMetrica/Dynamic/Core (3.15.1) + - YandexMobileMetrica/Dynamic/Crashes (3.15.1): - YandexMobileMetrica/Dynamic/Core DEPENDENCIES: - ActionSheetPicker-3.0 (= 2.7.1) - Agrume (= 5.6.13) - - Alamofire (= 5.4.2) + - Alamofire (= 5.4.3) - Amplitude (= 8.2.1) - Atributika (= 4.9.10) - BEMCheckBox (= 1.4.1) @@ -211,14 +230,14 @@ DEPENDENCIES: - EasyTipView (= 2.1.0) - FBSDKCoreKit (= 8.2.0) - FBSDKLoginKit (= 8.2.0) - - Firebase/Analytics (= 7.10.0) - - Firebase/Core (= 7.10.0) - - Firebase/Crashlytics (= 7.10.0) - - Firebase/Messaging (= 7.10.0) - - Firebase/RemoteConfig (= 7.10.0) + - Firebase/Analytics (= 7.11.0) + - Firebase/Core (= 7.11.0) + - Firebase/Crashlytics (= 7.11.0) + - Firebase/Messaging (= 7.11.0) + - Firebase/RemoteConfig (= 7.11.0) - FLEX (from `https://github.com/ivan-magda/FLEX.git`, branch `master`) - GoogleSignIn (= 5.0.2) - - Highlightr (= 2.1.0) + - Highlightr (from `https://github.com/raspu/Highlightr.git`, tag `2.1.2`) - IQKeyboardManagerSwift (= 6.5.6) - Kanna (= 5.2.2) - Koloda (= 5.0.1) @@ -243,7 +262,7 @@ DEPENDENCIES: - TTTAttributedLabel (= 2.0.0) - TUSafariActivity (= 1.0.4) - VK-ios-sdk (= 1.6.2) - - YandexMobileMetrica/Dynamic (= 3.15.0) + - YandexMobileMetrica/Dynamic (= 3.15.1) SPEC REPOS: https://github.com/CocoaPods/Specs.git: @@ -279,7 +298,6 @@ SPEC REPOS: - GTMAppAuth - GTMSessionFetcher - HexColors - - Highlightr - IQKeyboardManagerSwift - Kanna - Koloda @@ -313,6 +331,9 @@ EXTERNAL SOURCES: FLEX: :branch: master :git: https://github.com/ivan-magda/FLEX.git + Highlightr: + :git: https://github.com/raspu/Highlightr.git + :tag: 2.1.2 PanModal: :branch: remove-presenting-appearance-transitions :git: https://github.com/ivan-magda/PanModal.git @@ -326,6 +347,9 @@ CHECKOUT OPTIONS: FLEX: :commit: 99338e8545f70c143c88e2564e8b604ad4b285ae :git: https://github.com/ivan-magda/FLEX.git + Highlightr: + :git: https://github.com/raspu/Highlightr.git + :tag: 2.1.2 PanModal: :commit: 32fc8b5868b0254a2025c9c01b24c0e4b3fe537d :git: https://github.com/ivan-magda/PanModal.git @@ -339,7 +363,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: ActionSheetPicker-3.0: 36da254b97a09ff89679ecb8b8510bd3e5bdc773 Agrume: 21b96a1138abc0f890211bfcb12f8b1e3464b4c1 - Alamofire: bfbc4c2fe5909b1d94fb4ef2277c6b3727ef5dae + Alamofire: e447a2774a40c996748296fa2c55112fdbbc42f9 Amplitude: e91463935daea43afc81afa535b3d4d23043b0f5 AppAuth: 31bcec809a638d7bd2f86ea8a52bd45f6e81e7c7 Atributika: cf87f95f1d31d9cdb9af611a2b4e00672e9ae195 @@ -352,25 +376,25 @@ SPEC CHECKSUMS: EasyTipView: a92b6edc377b81c5ac18e9fd35d5ee78e9409488 FBSDKCoreKit: 4afd6ff53d8133a433dbcda44451c9498f8c6ce4 FBSDKLoginKit: 7181765f2524d7ebf82d9629066c8e6caafc99d0 - Firebase: fffddd0bab8677d07376538365faa93ff3889b39 - FirebaseABTesting: 457bc058cf6de8bcffc6fd9a2a4c933b24bfc264 - FirebaseAnalytics: 4641d7ae4220174f6ca5626163ffc5de2e90391e - FirebaseCore: ec566d917b2195fc2610aeb148dae99f57a788f9 - FirebaseCoreDiagnostics: 5662a3823ffcc0acbaa9a21ba5ed302fac634705 - FirebaseCrashlytics: e7669d368a22d202f1d0c7546ffdfdff496e1a8c - FirebaseInstallations: bf2ec8dbf36ff4c91af6b9a003d15855757680c1 - FirebaseInstanceID: 5ad92c898e1328b66e8dd58811964d6fe4d334c3 - FirebaseMessaging: 76b3058cef7f339cf10db196e03bbbb2165fb5d7 - FirebaseRemoteConfig: 2841e353accfe688150781deebeb245e3fe16c2f + Firebase: c121feb35e4126c0b355e3313fa9b487d47319fd + FirebaseABTesting: e66f1f80747792630d9b292966de206d5df9853b + FirebaseAnalytics: cd3bd84d722a24a8923918af8af8e5236f615d77 + FirebaseCore: 907447d8917a4d3eb0cce2829c5a0ad21d90b432 + FirebaseCoreDiagnostics: 68ad972f99206cef818230f3f3179d52ccfb7f8c + FirebaseCrashlytics: 272b675aa9d1e9bae1f9e1449fcc1f2cf6042806 + FirebaseInstallations: a58d4f72ec5861840b84df489f2668d970df558a + FirebaseInstanceID: ad5135045a498d7775903efd39762d2cdfa1be27 + FirebaseMessaging: 163435fb6db065e3b6228f1e577b10ed2cc506d2 + FirebaseRemoteConfig: 0ea30de5fb0231df8c1bdcdf3b6c23bdc5066131 FLEX: 75ca95cff4bd57592c6e75adee7651ace29f9c25 - GoogleAppMeasurement: 1c863b1161fc3c8cf614a7460d1be6a7c262aab3 + GoogleAppMeasurement: fd19169c3034975cb934e865e5667bfdce59df7f GoogleDataTransport: cd9db2180fcecd8da1b561aea31e3e56cf834aa7 GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213 GoogleUtilities: e1d9ed4e544fc32a93e00e721400cbc3f377200d GTMAppAuth: 5b53231ef6920f149ab84b2969cb0ab572da3077 GTMSessionFetcher: b3503b20a988c4e20cc189aa798fd18220133f52 HexColors: 6ad3947c3447a055a3aa8efa859def096351fe5f - Highlightr: 595f3e100737c8de41113385da8bd0b5b65212c6 + Highlightr: 683f05d5223cade533a78528a35c9f06e4caddf8 IQKeyboardManagerSwift: c7df9d2deb356c04522f5c4b7b6e4ce4d8ed94fe Kanna: 2c4eddaaa4bfb9a1ed19e53c0b3d42ef02838046 Koloda: d07b9199a383abc5898b62aa945a599f5e7c0c4b @@ -401,8 +425,8 @@ SPEC CHECKSUMS: TUSafariActivity: afc55a00965377939107ce4fdc7f951f62454546 URITemplate: 58e0d47f967006c5d59888af5356c4a8ed3b197d VK-ios-sdk: 5bcf00a2014a7323f98db9328b603d4f96635caa - YandexMobileMetrica: bf9b2bceb40811ab29c3d0f660ef13d381d8dd93 + YandexMobileMetrica: fb4b3c20a0c0237b03b34cc07b6d575332f43097 -PODFILE CHECKSUM: 3bb9bc441fd56cfb8f0d5d519ae99d2e89bac500 +PODFILE CHECKSUM: ec81cdf19dc5146ed521e3097ef3ceff23697e1e COCOAPODS: 1.10.1 diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index d055a96cf6..a314be0b2b 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -345,8 +345,10 @@ 0BCE34E8356EA252F3F2CD34 /* NewProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE29F0C2017792418DCA3A /* NewProfileViewController.swift */; }; 101D4F9FA505413E820316A3 /* FillBlanksQuizAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06455AE519D17C1948944F0E /* FillBlanksQuizAssembly.swift */; }; 10EE105C3899569C32A7E24C /* NewProfileAchievementsAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BA42C58C967F7FED2305306 /* NewProfileAchievementsAssembly.swift */; }; + 1232DCE0E54B310C809D031C /* LessonFinishedDemoPanModalDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB20549B185EBFAFD229A56 /* LessonFinishedDemoPanModalDataFlow.swift */; }; 12CB070461601452E35A6334 /* NewProfileUserActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CF7A377B4C8BC247ECBD29 /* NewProfileUserActivityViewController.swift */; }; 1403FF0C9A5259CF2D63D98C /* NewProfileUserActivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4E46A7B951582DFB9DA8130 /* NewProfileUserActivityProvider.swift */; }; + 14AD2486D6A2159C56EA464E /* LessonFinishedDemoPanModalOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C30C27A2878BBE9557F5BA /* LessonFinishedDemoPanModalOutputProtocol.swift */; }; 19BCC6AE85FA7F15895BFDD0 /* NewProfileCertificatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43830C1F0B97682B9CC38DB /* NewProfileCertificatesView.swift */; }; 1C748C5B088908FB323176B6 /* NewProfileSocialProfilesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9B98DD75AFD25B4028D8A48 /* NewProfileSocialProfilesProvider.swift */; }; 209E27FAF79E14028BA3E27E /* NewProfileStreakNotificationsDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0135D703CAD3B1C16BC95B63 /* NewProfileStreakNotificationsDataFlow.swift */; }; @@ -359,6 +361,7 @@ 2A7E1C8409B12A6CB09F0393 /* UserCoursesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A044A06FA4B2AA6C9F984A3 /* UserCoursesViewController.swift */; }; 2AFCCAAEBDBC4C05F4D5E5F0 /* DownloadARQuickLookDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A65C1DFD5A2533C506D297B /* DownloadARQuickLookDataFlow.swift */; }; 2BCB44322E830B821B671855 /* SimpleCourseListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E14855CB1F2EA03D94FEF0 /* SimpleCourseListInteractor.swift */; }; + 2C00A4D72632C75200AEE255 /* PanModalPresentableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C00A4D62632C75200AEE255 /* PanModalPresentableViewController.swift */; }; 2C01BB68233CD92C00C8DCF0 /* Require.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C01BB67233CD92C00C8DCF0 /* Require.swift */; }; 2C01D3AA22DDB7EA00C84CEE /* DefaultsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C01D3A922DDB7EA00C84CEE /* DefaultsContainer.swift */; }; 2C01D3AC22DDB98900C84CEE /* LaunchDefaultsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C01D3AB22DDB98900C84CEE /* LaunchDefaultsContainer.swift */; }; @@ -541,6 +544,8 @@ 2C48D604228F0EF700739477 /* ContentProcessingRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C48D603228F0EF700739477 /* ContentProcessingRule.swift */; }; 2C48D606228F114400739477 /* HTMLExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C48D605228F114400739477 /* HTMLExtractor.swift */; }; 2C48D608228F1E0800739477 /* ContentProcessingInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C48D607228F1E0800739477 /* ContentProcessingInjection.swift */; }; + 2C49BA8926331CF20000BB50 /* LessonOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C49BA8826331CF20000BB50 /* LessonOutputProtocol.swift */; }; + 2C49BA9626331E060000BB50 /* CourseInfoInputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C49BA9526331E060000BB50 /* CourseInfoInputProtocol.swift */; }; 2C4AD01123E2F3CD0049B7B0 /* DiscussionThread.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4AD01023E2F3CD0049B7B0 /* DiscussionThread.swift */; }; 2C4AD01323E2F3EC0049B7B0 /* DiscussionThread+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4AD01223E2F3EC0049B7B0 /* DiscussionThread+CoreDataProperties.swift */; }; 2C4AD01523E301C50049B7B0 /* DiscussionThreadsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4AD01423E301C50049B7B0 /* DiscussionThreadsAPI.swift */; }; @@ -1041,6 +1046,7 @@ 38C0348C761EF7B1032835EE /* FillBlanksQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 079BEAD644C47C1E37629DAF /* FillBlanksQuizView.swift */; }; 39AB2324EB824124A83D7534 /* UserCoursesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AF7B8FAACF8A4D31AF7583 /* UserCoursesInteractor.swift */; }; 3E467D12F5BA4AE24B7BB030 /* FillBlanksQuizPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40D8B41CD0EE056CA3361EEB /* FillBlanksQuizPresenter.swift */; }; + 3F8A824B6CDEA20CEC0DCC4F /* LessonFinishedDemoPanModalProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED3EA298A00E3C1DF87D33A7 /* LessonFinishedDemoPanModalProvider.swift */; }; 46115BB4590732AAB387046D /* NewProfileUserActivityAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61C03F8E163838C4E026CAE5 /* NewProfileUserActivityAssembly.swift */; }; 46A0BB292879D0FB31A504E7 /* CatalogBlocksInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA9ABFDBF465C10326697700 /* CatalogBlocksInteractor.swift */; }; 48B0EC48DA9989013F58FB2A /* CourseListFilterInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA93A7BBE66130A3D60D17A /* CourseListFilterInteractor.swift */; }; @@ -1572,6 +1578,8 @@ 96A3827B7E57E4B055221311 /* NewProfileUserActivityPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9217C4CCCAFEF3BDE7DD27A5 /* NewProfileUserActivityPresenter.swift */; }; 98230689D5C33F2FF95CD633 /* StepikAcademyCourseListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44CA1414D85B7D7645957E8F /* StepikAcademyCourseListViewController.swift */; }; 9953B53F378A70879B2FCDC7 /* SimpleCourseListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8CDB43E62B13FFD968CF503 /* SimpleCourseListView.swift */; }; + 9BA64E04E1D50A89885BFACD /* LessonFinishedDemoPanModalAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = F68EADA2E738B56A7547D295 /* LessonFinishedDemoPanModalAssembly.swift */; }; + 9E98A1AF097008C236DF309D /* LessonFinishedDemoPanModalPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC51547B4BFE999EA2BF12FC /* LessonFinishedDemoPanModalPresenter.swift */; }; 9FAD76150240B4F8C13ADFA2 /* NewProfileUserActivityInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE6AAA8E539D30FAF22B0B /* NewProfileUserActivityInteractor.swift */; }; A2E34812AB4F1593A4EB413B /* CourseListFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25A02BBBBC283CED244F6983 /* CourseListFilterView.swift */; }; A4ED8B7CACFF7AB41B163E79 /* NewProfileSocialProfilesInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E358732932D647EFD60F96E8 /* NewProfileSocialProfilesInteractor.swift */; }; @@ -1592,6 +1600,7 @@ CB429BDBAF7772B836FCA675 /* NewProfileStreakNotificationsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C6ED5894DADC5B17310B92 /* NewProfileStreakNotificationsPresenter.swift */; }; CC81627A3343CB1F28B98772 /* StepikAcademyCourseListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB7D664C83C2A5490B7145E /* StepikAcademyCourseListInteractor.swift */; }; D0EC355329C93B41463C75F9 /* NewProfileSocialProfilesAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77A061513F473CC4D8745A02 /* NewProfileSocialProfilesAssembly.swift */; }; + D305E595AB176E591584083C /* LessonFinishedDemoPanModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 481132AEA7BC453EB69DA98B /* LessonFinishedDemoPanModalView.swift */; }; D47D53DD6227E614E1DFCB9F /* NewProfileStreakNotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4026ACBBA0C3F084DA6D5A6E /* NewProfileStreakNotificationsView.swift */; }; D5817D0099BF67C57AB31DFB /* AuthorsCourseListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3CD51D524F420F6D7137B45 /* AuthorsCourseListViewController.swift */; }; D734AED171DF596EA2D282D2 /* NewProfileProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C028A8EC5C5E5E2E6ED03075 /* NewProfileProvider.swift */; }; @@ -1603,7 +1612,9 @@ DF2D0B6B2964A1E24CB373B2 /* TableQuizView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896F6AE51B2B7F26C576A308 /* TableQuizView.swift */; }; E17546DB452254955D100421 /* SubmissionsFilterPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75B4327DE945A7D6C60553E1 /* SubmissionsFilterPresenter.swift */; }; E25A7DCBFC93A1B63789122B /* NewProfileSocialProfilesDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89A4B39C167DC6CF26170396 /* NewProfileSocialProfilesDataFlow.swift */; }; + E2AEE9D34BA189E8BF96BE6B /* LessonFinishedDemoPanModalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFEE9D659FD4BC8B32B437E /* LessonFinishedDemoPanModalViewController.swift */; }; E36410A645B2D9008B9FD40B /* Pods_Stepic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F98579F526E5A4D162C3356 /* Pods_Stepic.framework */; }; + E3FA6135B20135AF33E347C2 /* LessonFinishedDemoPanModalInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BF050BD34E0B0A6DE33AE83 /* LessonFinishedDemoPanModalInteractor.swift */; }; E50909BCE8F32A2EB817ED1B /* SubmissionsFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D71CBD4CD4E534E50BEFFFC /* SubmissionsFilterView.swift */; }; E68D5D39475BA651C54FD4C9 /* NewProfileStreakNotificationsAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F0E49F1F2245F037EE6305 /* NewProfileStreakNotificationsAssembly.swift */; }; ECD21878641B360C12E8C585 /* NewProfileCreatedCoursesOutputProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD4A385A37C2127557150147 /* NewProfileCreatedCoursesOutputProtocol.swift */; }; @@ -2067,6 +2078,7 @@ 27CF7A377B4C8BC247ECBD29 /* NewProfileUserActivityViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileUserActivityViewController.swift; sourceTree = ""; }; 2C00186524C71404006C5094 /* Model_new_profile_created_courses_v56.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_new_profile_created_courses_v56.xcdatamodel; sourceTree = ""; }; 2C00396424E61B3000EBC7FF /* Model_assignment_progress_id_v64.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_assignment_progress_id_v64.xcdatamodel; sourceTree = ""; }; + 2C00A4D62632C75200AEE255 /* PanModalPresentableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanModalPresentableViewController.swift; sourceTree = ""; }; 2C01BB67233CD92C00C8DCF0 /* Require.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Require.swift; sourceTree = ""; }; 2C01D3A922DDB7EA00C84CEE /* DefaultsContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultsContainer.swift; sourceTree = ""; }; 2C01D3AB22DDB98900C84CEE /* LaunchDefaultsContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchDefaultsContainer.swift; sourceTree = ""; }; @@ -2272,6 +2284,8 @@ 2C48D603228F0EF700739477 /* ContentProcessingRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentProcessingRule.swift; sourceTree = ""; }; 2C48D605228F114400739477 /* HTMLExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLExtractor.swift; sourceTree = ""; }; 2C48D607228F1E0800739477 /* ContentProcessingInjection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentProcessingInjection.swift; sourceTree = ""; }; + 2C49BA8826331CF20000BB50 /* LessonOutputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonOutputProtocol.swift; sourceTree = ""; }; + 2C49BA9526331E060000BB50 /* CourseInfoInputProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseInfoInputProtocol.swift; sourceTree = ""; }; 2C4AD00E23E2E6550049B7B0 /* Model_ discussion_threads.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model_ discussion_threads.xcdatamodel"; sourceTree = ""; }; 2C4AD01023E2F3CD0049B7B0 /* DiscussionThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscussionThread.swift; sourceTree = ""; }; 2C4AD01223E2F3EC0049B7B0 /* DiscussionThread+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DiscussionThread+CoreDataProperties.swift"; sourceTree = ""; }; @@ -2810,6 +2824,7 @@ 424840FC832A3CF79ACD98CF /* StepikAcademyCourseListPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StepikAcademyCourseListPresenter.swift; sourceTree = ""; }; 4356ADE389830A4DB65CC0D7 /* CatalogBlocksViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CatalogBlocksViewController.swift; sourceTree = ""; }; 44CA1414D85B7D7645957E8F /* StepikAcademyCourseListViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StepikAcademyCourseListViewController.swift; sourceTree = ""; }; + 481132AEA7BC453EB69DA98B /* LessonFinishedDemoPanModalView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LessonFinishedDemoPanModalView.swift; sourceTree = ""; }; 489380E1AE5474348FB1450D /* Pods-Stepic.production debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stepic.production debug.xcconfig"; path = "Target Support Files/Pods-Stepic/Pods-Stepic.production debug.xcconfig"; sourceTree = ""; }; 49B8797DC84D64C5BAA84E76 /* Pods-Stepic.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stepic.debug.xcconfig"; path = "Target Support Files/Pods-Stepic/Pods-Stepic.debug.xcconfig"; sourceTree = ""; }; 5099C818F5A180372348FC30 /* NewProfileCreatedCoursesDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileCreatedCoursesDataFlow.swift; sourceTree = ""; }; @@ -3297,6 +3312,7 @@ 66E14855CB1F2EA03D94FEF0 /* SimpleCourseListInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SimpleCourseListInteractor.swift; sourceTree = ""; }; 69DE4213EB9F9BB5C2C25E78 /* NewProfileInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileInteractor.swift; sourceTree = ""; }; 6A5A3AC14BE54DE59AD0C0DE /* NewProfileCreatedCoursesAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileCreatedCoursesAssembly.swift; sourceTree = ""; }; + 6BF050BD34E0B0A6DE33AE83 /* LessonFinishedDemoPanModalInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LessonFinishedDemoPanModalInteractor.swift; sourceTree = ""; }; 6D71CBD4CD4E534E50BEFFFC /* SubmissionsFilterView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SubmissionsFilterView.swift; sourceTree = ""; }; 6EC7EEE6C9EC897044BAEBA7 /* AuthorsCourseListPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AuthorsCourseListPresenter.swift; sourceTree = ""; }; 71B79C2BD72E891ECD68BD6A /* CourseListFilterOutputProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CourseListFilterOutputProtocol.swift; sourceTree = ""; }; @@ -3336,6 +3352,7 @@ 9E4F5E8235E0245E5D5CB6BC /* NewProfileCertificatesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileCertificatesProvider.swift; sourceTree = ""; }; 9EC26997F83B299C9E3FCC5D /* UserCoursesAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UserCoursesAssembly.swift; sourceTree = ""; }; 9F98579F526E5A4D162C3356 /* Pods_Stepic.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Stepic.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9FB20549B185EBFAFD229A56 /* LessonFinishedDemoPanModalDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LessonFinishedDemoPanModalDataFlow.swift; sourceTree = ""; }; A02FE3CE59DBF4D12871ED55 /* AuthorsCourseListProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AuthorsCourseListProvider.swift; sourceTree = ""; }; A1A67C6A3908851371ABC4C8 /* NewProfileDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileDataFlow.swift; sourceTree = ""; }; A25E416101A848548101C7DA /* StepikAcademyCourseListDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StepikAcademyCourseListDataFlow.swift; sourceTree = ""; }; @@ -3352,7 +3369,9 @@ B9BBB601E6D7DA95CDE19CDE /* SimpleCourseListPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SimpleCourseListPresenter.swift; sourceTree = ""; }; BA9ABFDBF465C10326697700 /* CatalogBlocksInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CatalogBlocksInteractor.swift; sourceTree = ""; }; BAF219462BE0AB704DED8077 /* UserCoursesView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UserCoursesView.swift; sourceTree = ""; }; + BAFEE9D659FD4BC8B32B437E /* LessonFinishedDemoPanModalViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LessonFinishedDemoPanModalViewController.swift; sourceTree = ""; }; BC0BA4BBDDD434836B383B7A /* NewProfileSocialProfilesViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileSocialProfilesViewController.swift; sourceTree = ""; }; + BC51547B4BFE999EA2BF12FC /* LessonFinishedDemoPanModalPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LessonFinishedDemoPanModalPresenter.swift; sourceTree = ""; }; BC88362446F322F9B299287F /* NewProfileCertificatesViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileCertificatesViewController.swift; sourceTree = ""; }; BE6A5638023A16AE15886AB8 /* DebugMenuView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DebugMenuView.swift; sourceTree = ""; }; C028A8EC5C5E5E2E6ED03075 /* NewProfileProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileProvider.swift; sourceTree = ""; }; @@ -3368,6 +3387,7 @@ D109E72D69B31373C97237F8 /* Pods-Stepic.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stepic.release.xcconfig"; path = "Target Support Files/Pods-Stepic/Pods-Stepic.release.xcconfig"; sourceTree = ""; }; D1BB507BCF6F48523222D6D3 /* NewProfileAchievementsPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileAchievementsPresenter.swift; sourceTree = ""; }; D22422E57330EA3D5729E037 /* Pods-StepicTests.develop debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StepicTests.develop debug.xcconfig"; path = "Target Support Files/Pods-StepicTests/Pods-StepicTests.develop debug.xcconfig"; sourceTree = ""; }; + D2C30C27A2878BBE9557F5BA /* LessonFinishedDemoPanModalOutputProtocol.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LessonFinishedDemoPanModalOutputProtocol.swift; sourceTree = ""; }; D43830C1F0B97682B9CC38DB /* NewProfileCertificatesView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileCertificatesView.swift; sourceTree = ""; }; D6FF71A3DD5EA47EA1BD4084 /* NewProfileAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileAssembly.swift; sourceTree = ""; }; D7FDCEA2CEBE8396085B5648 /* NewProfileCreatedCoursesView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileCreatedCoursesView.swift; sourceTree = ""; }; @@ -3380,10 +3400,12 @@ EA3DD53832CC0C786A41D65C /* DebugMenuInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DebugMenuInteractor.swift; sourceTree = ""; }; EB3FB0B427E038E36BA99318 /* CatalogBlocksDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CatalogBlocksDataFlow.swift; sourceTree = ""; }; ECD9168AF529D0D527B1DCDB /* Pods-StepicTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StepicTests.debug.xcconfig"; path = "Target Support Files/Pods-StepicTests/Pods-StepicTests.debug.xcconfig"; sourceTree = ""; }; + ED3EA298A00E3C1DF87D33A7 /* LessonFinishedDemoPanModalProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LessonFinishedDemoPanModalProvider.swift; sourceTree = ""; }; EF17244B78AAAC345805C0DA /* NewProfileAchievementsViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileAchievementsViewController.swift; sourceTree = ""; }; F09E6F2AFFE7DFA834D0EA1E /* AuthorsCourseListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AuthorsCourseListView.swift; sourceTree = ""; }; F4B98B071C1C8E3FB13F9717 /* NewProfileAchievementsInteractor.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileAchievementsInteractor.swift; sourceTree = ""; }; F4E46A7B951582DFB9DA8130 /* NewProfileUserActivityProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NewProfileUserActivityProvider.swift; sourceTree = ""; }; + F68EADA2E738B56A7547D295 /* LessonFinishedDemoPanModalAssembly.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = LessonFinishedDemoPanModalAssembly.swift; sourceTree = ""; }; F7F8266FC6C7815F9D8AA8A8 /* CourseListFilterPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CourseListFilterPresenter.swift; sourceTree = ""; }; F8336942AE67DE9A75C7FC37 /* SubmissionsFilterDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SubmissionsFilterDataFlow.swift; sourceTree = ""; }; F8CDB43E62B13FFD968CF503 /* SimpleCourseListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SimpleCourseListView.swift; sourceTree = ""; }; @@ -3610,6 +3632,14 @@ path = InputOutput; sourceTree = ""; }; + 2C00A4E22632CB6600AEE255 /* LessonPanModals */ = { + isa = PBXGroup; + children = ( + 878FD9C3B666F79425AE0E95 /* LessonFinishedDemoPanModal */, + ); + path = LessonPanModals; + sourceTree = ""; + }; 2C0176C32188A49100DDB9D0 /* Analytics */ = { isa = PBXGroup; children = ( @@ -3964,6 +3994,22 @@ path = ContentProcessor; sourceTree = ""; }; + 2C49BA8726331CC80000BB50 /* InputOutput */ = { + isa = PBXGroup; + children = ( + 2C49BA8826331CF20000BB50 /* LessonOutputProtocol.swift */, + ); + path = InputOutput; + sourceTree = ""; + }; + 2C49BA9426331DE40000BB50 /* InputOutput */ = { + isa = PBXGroup; + children = ( + 2C49BA9526331E060000BB50 /* CourseInfoInputProtocol.swift */, + ); + path = InputOutput; + sourceTree = ""; + }; 2C4BBF0B203DC5C9000A4250 /* Plyr */ = { isa = PBXGroup; children = ( @@ -7221,6 +7267,7 @@ 62E98467D5F10A9715F7FABA /* Home */, 2C03627F2456DE0F00551807 /* HomeSubmodules */, 62E981F9A38A8FB18756836C /* Lesson */, + 2C00A4E22632CB6600AEE255 /* LessonPanModals */, DF13C56653F1B922734D4272 /* NewProfile */, 2C8BCC23248699F700DFB009 /* NewProfileSubmodules */, 62E9888B323F8D7D6B0F33B7 /* ProfileEdit */, @@ -7273,6 +7320,7 @@ 62E983EC489566F23F209BE7 /* LessonProvider.swift */, 62E987AE3DC45621DD9315B4 /* LessonViewController.swift */, 62E985487504F29DB2BB4077 /* LessonViewModel.swift */, + 2C49BA8726331CC80000BB50 /* InputOutput */, 62E98D4C608A990D7F26CCAD /* Views */, ); path = Lesson; @@ -7473,6 +7521,7 @@ children = ( 2C3ABA8823D1DA8D00E90439 /* AboutAppViewController.swift */, 62E9811E14B07132959D4987 /* ContactSupportController.swift */, + 2C00A4D62632C75200AEE255 /* PanModalPresentableViewController.swift */, 2C8F3ADA23CC8533004D113A /* SelectItemTableViewController.swift */, 62E98C04C47BEAACB0A48B7A /* StyledNavigationController.swift */, 081387E01D7AF7700092E05D /* StyledTabBarViewController.swift */, @@ -7845,6 +7894,7 @@ 62E989090B035BF6860C25CF /* CourseInfoProvider.swift */, 62E9809834661AB109B65859 /* CourseInfoSubmoduleProtocol.swift */, 62E98CABA3220071A81B52D2 /* CourseInfoViewController.swift */, + 2C49BA9426331DE40000BB50 /* InputOutput */, 62E98C451C338B7FCE8E1DFC /* Views */, ); path = CourseInfo; @@ -8496,6 +8546,21 @@ path = SimpleCourseList; sourceTree = ""; }; + 878FD9C3B666F79425AE0E95 /* LessonFinishedDemoPanModal */ = { + isa = PBXGroup; + children = ( + F68EADA2E738B56A7547D295 /* LessonFinishedDemoPanModalAssembly.swift */, + 9FB20549B185EBFAFD229A56 /* LessonFinishedDemoPanModalDataFlow.swift */, + 6BF050BD34E0B0A6DE33AE83 /* LessonFinishedDemoPanModalInteractor.swift */, + BC51547B4BFE999EA2BF12FC /* LessonFinishedDemoPanModalPresenter.swift */, + ED3EA298A00E3C1DF87D33A7 /* LessonFinishedDemoPanModalProvider.swift */, + 481132AEA7BC453EB69DA98B /* LessonFinishedDemoPanModalView.swift */, + BAFEE9D659FD4BC8B32B437E /* LessonFinishedDemoPanModalViewController.swift */, + FC5E804D3F50F3ED2BE4AEFC /* InputOutput */, + ); + path = LessonFinishedDemoPanModal; + sourceTree = ""; + }; 9C37FBB4DB62F0DC3CDFFE7A /* InputOutput */ = { isa = PBXGroup; children = ( @@ -8687,6 +8752,14 @@ path = CatalogBlocks; sourceTree = ""; }; + FC5E804D3F50F3ED2BE4AEFC /* InputOutput */ = { + isa = PBXGroup; + children = ( + D2C30C27A2878BBE9557F5BA /* LessonFinishedDemoPanModalOutputProtocol.swift */, + ); + path = InputOutput; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -9664,6 +9737,7 @@ 2C50363B24C58A43001FAE04 /* NewProfileCertificatesViewModel.swift in Sources */, 2C2385B024E3C38F0049BA42 /* FullscreenImageViewer.swift in Sources */, 2C51E1E125B8A34000E38DF8 /* AnalyticsStorageManager.swift in Sources */, + 2C00A4D72632C75200AEE255 /* PanModalPresentableViewController.swift in Sources */, 08F485AF1C58E946000165AA /* SortingDataset.swift in Sources */, 2CF1B33E2163BE720008DA0C /* StoriesAssembly.swift in Sources */, 2C04BA542407C3BF00D74D4B /* AttemptsPersistenceService.swift in Sources */, @@ -9812,6 +9886,7 @@ 0813EEA61BFE5A5400DB4B83 /* Assignment+CoreDataProperties.swift in Sources */, 2C284DB92474418600669736 /* CoursePaymentsAPI.swift in Sources */, 080C5E7B1EFC13ED0036EB3D /* CodeSample.swift in Sources */, + 2C49BA8926331CF20000BB50 /* LessonOutputProtocol.swift in Sources */, 2CB37E6623902BB80050D85E /* StorageUsageService.swift in Sources */, 2C2E44D524FE5FEA006B7303 /* VisitedCoursesCleaner.swift in Sources */, 2C2C4D91246B485400CF759D /* UIModalPresentationStyle+Fallback.swift in Sources */, @@ -10631,6 +10706,7 @@ 62E98961F7A3F44BF56A5B0D /* ChoiceElementView.swift in Sources */, 62E9843FDBD476D56D82A5EA /* NewChoiceQuizView.swift in Sources */, 62E987293FC3D84A80DB1FF4 /* NewFreeAnswerQuizAssembly.swift in Sources */, + 2C49BA9626331E060000BB50 /* CourseInfoInputProtocol.swift in Sources */, 62E98A49D3861B999950F146 /* NewFreeAnswerQuizDataFlow.swift in Sources */, 62E98F77A66AF9AE781611EF /* NewFreeAnswerQuizInteractor.swift in Sources */, 62E988898D30EFB35E3050DC /* NewFreeAnswerQuizPresenter.swift in Sources */, @@ -10911,6 +10987,14 @@ 7A60902EC11819293528016A /* DebugMenuProvider.swift in Sources */, 50D93D7BBB977C8B41A138EF /* DebugMenuView.swift in Sources */, 5074C3C1E642E250F18B75D8 /* DebugMenuViewController.swift in Sources */, + 9BA64E04E1D50A89885BFACD /* LessonFinishedDemoPanModalAssembly.swift in Sources */, + 1232DCE0E54B310C809D031C /* LessonFinishedDemoPanModalDataFlow.swift in Sources */, + E3FA6135B20135AF33E347C2 /* LessonFinishedDemoPanModalInteractor.swift in Sources */, + 9E98A1AF097008C236DF309D /* LessonFinishedDemoPanModalPresenter.swift in Sources */, + 3F8A824B6CDEA20CEC0DCC4F /* LessonFinishedDemoPanModalProvider.swift in Sources */, + D305E595AB176E591584083C /* LessonFinishedDemoPanModalView.swift in Sources */, + E2AEE9D34BA189E8BF96BE6B /* LessonFinishedDemoPanModalViewController.swift in Sources */, + 14AD2486D6A2159C56EA464E /* LessonFinishedDemoPanModalOutputProtocol.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -11171,7 +11255,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Production.plist"; @@ -11196,7 +11280,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Production.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; @@ -11338,7 +11422,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Production.plist"; @@ -11368,7 +11452,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64; @@ -11459,7 +11543,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Develop.plist"; @@ -11511,7 +11595,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Develop.plist"; @@ -11592,7 +11676,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Develop.plist"; @@ -11640,7 +11724,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Develop.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; @@ -12161,7 +12245,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Release.plist"; @@ -12215,7 +12299,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Release.plist"; @@ -12297,7 +12381,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; ENABLE_BITCODE = YES; INFOPLIST_FILE = "Stepic/Info-Release.plist"; @@ -12345,7 +12429,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 324; + CURRENT_PROJECT_VERSION = 326; DEVELOPMENT_TEAM = UJ4KC2QN7B; INFOPLIST_FILE = "StickerPackExtension/Info-Release.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; diff --git a/Stepic/Images.xcassets/LessonPanModals/Contents.json b/Stepic/Images.xcassets/LessonPanModals/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/Stepic/Images.xcassets/LessonPanModals/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Stepic/Images.xcassets/LessonPanModals/finished-demo-lesson-modal-header.imageset/Contents.json b/Stepic/Images.xcassets/LessonPanModals/finished-demo-lesson-modal-header.imageset/Contents.json new file mode 100644 index 0000000000..dbc9203498 --- /dev/null +++ b/Stepic/Images.xcassets/LessonPanModals/finished-demo-lesson-modal-header.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "finished-demo-lesson-modal-header.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Stepic/Images.xcassets/LessonPanModals/finished-demo-lesson-modal-header.imageset/finished-demo-lesson-modal-header.pdf b/Stepic/Images.xcassets/LessonPanModals/finished-demo-lesson-modal-header.imageset/finished-demo-lesson-modal-header.pdf new file mode 100644 index 0000000000..66fcac24d0 Binary files /dev/null and b/Stepic/Images.xcassets/LessonPanModals/finished-demo-lesson-modal-header.imageset/finished-demo-lesson-modal-header.pdf differ diff --git a/Stepic/Info-Develop.plist b/Stepic/Info-Develop.plist index 5f148e7069..dde1907c1f 100644 --- a/Stepic/Info-Develop.plist +++ b/Stepic/Info-Develop.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.170-develop + 1.171-develop CFBundleSignature ???? CFBundleURLTypes @@ -62,7 +62,7 @@ CFBundleVersion - 324 + 326 FacebookAppID 171127739724012 FacebookDisplayName diff --git a/Stepic/Info-Production.plist b/Stepic/Info-Production.plist index e5cd11fffb..162f476072 100644 --- a/Stepic/Info-Production.plist +++ b/Stepic/Info-Production.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.170 + 1.171 CFBundleSignature ???? CFBundleURLTypes @@ -62,7 +62,7 @@ CFBundleVersion - 324 + 326 FacebookAppID 171127739724012 FacebookDisplayName diff --git a/Stepic/Info-Release.plist b/Stepic/Info-Release.plist index 50d1770849..ab3707d059 100644 --- a/Stepic/Info-Release.plist +++ b/Stepic/Info-Release.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.170-release + 1.171-release CFBundleSignature ???? CFBundleURLTypes @@ -62,7 +62,7 @@ CFBundleVersion - 324 + 326 FacebookAppID 171127739724012 FacebookDisplayName diff --git a/Stepic/Legacy/Analytics/Events/AmplitudeAnalyticsEvents.swift b/Stepic/Legacy/Analytics/Events/AmplitudeAnalyticsEvents.swift index e7bb7ab6fd..3662aaca6a 100644 --- a/Stepic/Legacy/Analytics/Events/AmplitudeAnalyticsEvents.swift +++ b/Stepic/Legacy/Analytics/Events/AmplitudeAnalyticsEvents.swift @@ -465,6 +465,26 @@ extension AnalyticsEvent { return AmplitudeAnalyticsEvent(name: "Font size selected", parameters: ["size": fontSizeStringValue]) } + // MARK: - Content Language - + + static func contentLanguageChanged( + _ contentLanguage: ContentLanguage, + source: ContentLanguageChangeSource + ) -> AmplitudeAnalyticsEvent { + AmplitudeAnalyticsEvent( + name: "Content language changed", + parameters: [ + "language": contentLanguage.languageString, + "source": source.rawValue + ] + ) + } + + enum ContentLanguageChangeSource: String { + case catalog + case settings + } + // MARK: - CoursePreview - static func coursePreviewScreenOpened(course: Course, viewSource: CourseViewSource) -> AmplitudeAnalyticsEvent { diff --git a/Stepic/Legacy/Analytics/Events/StepikAnalyticsEvents.swift b/Stepic/Legacy/Analytics/Events/StepikAnalyticsEvents.swift index 3966324989..0115083441 100644 --- a/Stepic/Legacy/Analytics/Events/StepikAnalyticsEvents.swift +++ b/Stepic/Legacy/Analytics/Events/StepikAnalyticsEvents.swift @@ -1,6 +1,8 @@ import Foundation extension AnalyticsEvent { + // MARK: - Catalog - + static func catalogDisplay( courseID: Course.IdType, viewSource: CourseViewSource, @@ -64,4 +66,18 @@ extension AnalyticsEvent { return params } + + // MARK: - BuyClick - + + static func buyCoursePressed(id: Course.IdType) -> StepikAnalyticsEvent { + let name = "buy-course-pressed" + + let params: [String: Any] = [ + "data": ["course": id], + "name": name, + "timestamp": Date().timeIntervalSince1970 + ] + + return StepikAnalyticsEvent(name: name, parameters: params) + } } diff --git a/Stepic/Legacy/Services/DeepLinks/StepsControllerDeepLinkRouter.swift b/Stepic/Legacy/Services/DeepLinks/StepsControllerDeepLinkRouter.swift index 9d2a5fa8ab..181ca90b4e 100644 --- a/Stepic/Legacy/Services/DeepLinks/StepsControllerDeepLinkRouter.swift +++ b/Stepic/Legacy/Services/DeepLinks/StepsControllerDeepLinkRouter.swift @@ -195,22 +195,26 @@ final class StepsControllerDeepLinkRouter: NSObject { initialTab: .syllabus, courseViewSource: self.courseViewSource ) + let courseInfoViewController = courseInfoAssembly.makeModule() + let lessonAssemblyWithoutUnit = LessonAssembly( initialContext: .lesson(id: lesson.id), - startStep: .index(stepId - 1) + startStep: .index(stepId - 1), + moduleOutput: courseInfoAssembly.moduleInput as? LessonOutputProtocol ) var controllersStack: [UIViewController] = [] if section.isExam { - controllersStack.append(courseInfoAssembly.makeModule()) + controllersStack.append(courseInfoViewController) } else if includeUnit { - controllersStack.append(courseInfoAssembly.makeModule()) + controllersStack.append(courseInfoViewController) if let unit = currentUnit { let lessonAssembly = LessonAssembly( initialContext: .unit(id: unit.id), - startStep: .index(stepId - 1) + startStep: .index(stepId - 1), + moduleOutput: courseInfoAssembly.moduleInput as? LessonOutputProtocol ) controllersStack.append(lessonAssembly.makeModule()) } else { diff --git a/Stepic/Legacy/TransitionRouters/LastStepRouter.swift b/Stepic/Legacy/TransitionRouters/LastStepRouter.swift index 9175533fc7..ed2579c991 100644 --- a/Stepic/Legacy/TransitionRouters/LastStepRouter.swift +++ b/Stepic/Legacy/TransitionRouters/LastStepRouter.swift @@ -102,11 +102,12 @@ final class LastStepRouter { return } - let courseInfoController = CourseInfoAssembly( + let courseInfoAssembly = CourseInfoAssembly( courseID: course.id, initialTab: .syllabus, courseViewSource: courseViewSource - ).makeModule() + ) + let courseInfoController = courseInfoAssembly.makeModule() func openSyllabus() { SVProgressHUD.showSuccess(withStatus: "") @@ -214,7 +215,8 @@ final class LastStepRouter { stepIDPromise.done { targetStepID in let lessonAssembly = LessonAssembly( initialContext: .unit(id: unit.id), - startStep: .id(targetStepID) + startStep: .id(targetStepID), + moduleOutput: courseInfoAssembly.moduleInput as? LessonOutputProtocol ) SVProgressHUD.showSuccess(withStatus: "") diff --git a/Stepic/Sources/Common/LayoutInsets.swift b/Stepic/Sources/Common/LayoutInsets.swift index f4af396490..4f67e49b1d 100644 --- a/Stepic/Sources/Common/LayoutInsets.swift +++ b/Stepic/Sources/Common/LayoutInsets.swift @@ -56,4 +56,18 @@ struct LayoutInsets { self.rightInset = inset self.bottomInset = inset } + + init(horizontal: CGFloat) { + self.topInset = nil + self.leftInset = horizontal + self.rightInset = horizontal + self.bottomInset = nil + } + + init(vertical: CGFloat) { + self.topInset = vertical + self.leftInset = nil + self.rightInset = nil + self.bottomInset = vertical + } } diff --git a/Stepic/Sources/Controllers/PanModalPresentableViewController.swift b/Stepic/Sources/Controllers/PanModalPresentableViewController.swift new file mode 100644 index 0000000000..14e6a371cc --- /dev/null +++ b/Stepic/Sources/Controllers/PanModalPresentableViewController.swift @@ -0,0 +1,54 @@ +import PanModal +import UIKit + +class PanModalPresentableViewController: UIViewController, PanModalPresentable { + var panScrollable: UIScrollView? { nil } + + var shortFormHeight: PanModalHeight { + self.isShortFormEnabled + ? .contentHeight(floor(UIScreen.main.bounds.height / 3)) + : self.longFormHeight + } + + var anchorModalToLongForm: Bool { false } + + var isShortFormEnabled = true + + init() { + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + self.updateAdditionalSafeAreaInsets() + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + coordinator.animate( + alongsideTransition: { _ in + self.updateAdditionalSafeAreaInsets() + }, + completion: nil + ) + } + + func willTransition(to state: PanModalPresentationController.PresentationState) { + guard self.isShortFormEnabled, case .longForm = state else { + return + } + + self.isShortFormEnabled = false + self.panModalSetNeedsLayoutUpdate() + } + + private func updateAdditionalSafeAreaInsets() { + self.additionalSafeAreaInsets = UIApplication.shared.delegate?.window??.safeAreaInsets ?? .zero + } +} diff --git a/Stepic/Sources/Frameworks/Analytics/Analytics.swift b/Stepic/Sources/Frameworks/Analytics/Analytics.swift index 894b5d3c21..e0277d0ae9 100644 --- a/Stepic/Sources/Frameworks/Analytics/Analytics.swift +++ b/Stepic/Sources/Frameworks/Analytics/Analytics.swift @@ -8,6 +8,12 @@ extension Analytics { func send(_ event: AnalyticsEvent) { self.send(event, forceSend: false) } + + func send(_ events: AnalyticsEvent..., forceSend: Bool = false) { + events.forEach { event in + self.send(event, forceSend: forceSend) + } + } } final class StepikAnalytics: Analytics { diff --git a/Stepic/Sources/Modules/CourseInfo/CourseInfoAssembly.swift b/Stepic/Sources/Modules/CourseInfo/CourseInfoAssembly.swift index 6f3474a719..d48c60032d 100644 --- a/Stepic/Sources/Modules/CourseInfo/CourseInfoAssembly.swift +++ b/Stepic/Sources/Modules/CourseInfo/CourseInfoAssembly.swift @@ -1,6 +1,8 @@ import UIKit final class CourseInfoAssembly: Assembly { + var moduleInput: CourseInfoInputProtocol? + private let courseID: Course.IdType private let initialTab: CourseInfo.Tab private let didJustSubscribe: Bool @@ -84,6 +86,7 @@ final class CourseInfoAssembly: Assembly { didJustSubscribe: self.didJustSubscribe ) presenter.viewController = viewController + self.moduleInput = interactor return viewController } diff --git a/Stepic/Sources/Modules/CourseInfo/CourseInfoDataFlow.swift b/Stepic/Sources/Modules/CourseInfo/CourseInfoDataFlow.swift index c2964c64eb..18c3af47ce 100644 --- a/Stepic/Sources/Modules/CourseInfo/CourseInfoDataFlow.swift +++ b/Stepic/Sources/Modules/CourseInfo/CourseInfoDataFlow.swift @@ -177,6 +177,13 @@ enum CourseInfo { struct Request {} } + /// Pop lesson module and do main course action + enum LessonModuleBuyCourseActionPresentation { + struct Response {} + + struct ViewModel {} + } + /// Do try for free action -> open preview lesson by id enum PreviewLessonPresentation { struct Request {} diff --git a/Stepic/Sources/Modules/CourseInfo/CourseInfoInteractor.swift b/Stepic/Sources/Modules/CourseInfo/CourseInfoInteractor.swift index e31565c35d..31a9cdd486 100644 --- a/Stepic/Sources/Modules/CourseInfo/CourseInfoInteractor.swift +++ b/Stepic/Sources/Modules/CourseInfo/CourseInfoInteractor.swift @@ -253,7 +253,10 @@ final class CourseInfoInteractor: CourseInfoInteractorProtocol { } else { // Paid course -> open web page if course.isPaid && !course.isPurchased { - self.analytics.send(.courseBuyPressed(source: .courseScreen, id: course.id)) + self.analytics.send( + .buyCoursePressed(id: course.id), + .courseBuyPressed(source: .courseScreen, id: course.id) + ) if self.iapService.canBuyCourse(course) { self.iapService.buy(course: course, delegate: self) @@ -429,6 +432,17 @@ final class CourseInfoInteractor: CourseInfoInteractorProtocol { } } +// MARK: - CourseInfoInteractor: CourseInfoInputProtocol - +extension CourseInfoInteractor: CourseInfoInputProtocol {} + +// MARK: - CourseInfoInteractor: LessonOutputProtocol - + +extension CourseInfoInteractor: LessonOutputProtocol { + func handleLessonDidRequestBuyCourse() { + self.presenter.presentLessonModuleBuyCourseAction(response: .init()) + } +} + // MARK: - CourseInfoInteractor: CourseInfoTabSyllabusOutputProtocol - extension CourseInfoInteractor: CourseInfoTabSyllabusOutputProtocol { diff --git a/Stepic/Sources/Modules/CourseInfo/CourseInfoPresenter.swift b/Stepic/Sources/Modules/CourseInfo/CourseInfoPresenter.swift index bb8601624e..53b1e57ef7 100644 --- a/Stepic/Sources/Modules/CourseInfo/CourseInfoPresenter.swift +++ b/Stepic/Sources/Modules/CourseInfo/CourseInfoPresenter.swift @@ -7,6 +7,7 @@ protocol CourseInfoPresenterProtocol { func presentExamLesson(response: CourseInfo.ExamLessonPresentation.Response) func presentCourseSharing(response: CourseInfo.CourseShareAction.Response) func presentLastStep(response: CourseInfo.LastStepPresentation.Response) + func presentLessonModuleBuyCourseAction(response: CourseInfo.LessonModuleBuyCourseActionPresentation.Response) func presentPreviewLesson(response: CourseInfo.PreviewLessonPresentation.Response) func presentAuthorization(response: CourseInfo.AuthorizationPresentation.Response) func presentPaidCourseBuying(response: CourseInfo.PaidCourseBuyingPresentation.Response) @@ -80,6 +81,10 @@ final class CourseInfoPresenter: CourseInfoPresenterProtocol { ) } + func presentLessonModuleBuyCourseAction(response: CourseInfo.LessonModuleBuyCourseActionPresentation.Response) { + self.viewController?.displayLessonModuleBuyCourseAction(viewModel: .init()) + } + func presentPreviewLesson(response: CourseInfo.PreviewLessonPresentation.Response) { self.viewController?.displayPreviewLesson(viewModel: .init(previewLessonID: response.previewLessonID)) } diff --git a/Stepic/Sources/Modules/CourseInfo/CourseInfoViewController.swift b/Stepic/Sources/Modules/CourseInfo/CourseInfoViewController.swift index d132a949e8..bcb438da1f 100644 --- a/Stepic/Sources/Modules/CourseInfo/CourseInfoViewController.swift +++ b/Stepic/Sources/Modules/CourseInfo/CourseInfoViewController.swift @@ -18,6 +18,7 @@ protocol CourseInfoViewControllerProtocol: AnyObject { func displayExamLesson(viewModel: CourseInfo.ExamLessonPresentation.ViewModel) func displayCourseSharing(viewModel: CourseInfo.CourseShareAction.ViewModel) func displayLastStep(viewModel: CourseInfo.LastStepPresentation.ViewModel) + func displayLessonModuleBuyCourseAction(viewModel: CourseInfo.LessonModuleBuyCourseActionPresentation.ViewModel) func displayPreviewLesson(viewModel: CourseInfo.PreviewLessonPresentation.ViewModel) func displayAuthorization(viewModel: CourseInfo.AuthorizationPresentation.ViewModel) func displayPaidCourseBuying(viewModel: CourseInfo.PaidCourseBuyingPresentation.ViewModel) @@ -487,7 +488,10 @@ extension CourseInfoViewController: CourseInfoViewControllerProtocol { } func displayLesson(viewModel: CourseInfo.LessonPresentation.ViewModel) { - let assembly = LessonAssembly(initialContext: .unit(id: viewModel.unitID)) + let assembly = LessonAssembly( + initialContext: .unit(id: viewModel.unitID), + moduleOutput: self.interactor as? LessonOutputProtocol + ) self.push(module: assembly.makeModule()) } @@ -553,8 +557,24 @@ extension CourseInfoViewController: CourseInfoViewControllerProtocol { ) } + func displayLessonModuleBuyCourseAction(viewModel: CourseInfo.LessonModuleBuyCourseActionPresentation.ViewModel) { + guard let navigationController = self.navigationController, + navigationController.topViewController?.isKind(of: LessonViewController.self) ?? false else { + return + } + + navigationController.popViewController(animated: true) + + DispatchQueue.main.async { + self.interactor.doMainCourseAction(request: .init()) + } + } + func displayPreviewLesson(viewModel: CourseInfo.PreviewLessonPresentation.ViewModel) { - let assembly = LessonAssembly(initialContext: .lesson(id: viewModel.previewLessonID)) + let assembly = LessonAssembly( + initialContext: .lesson(id: viewModel.previewLessonID), + moduleOutput: self.interactor as? LessonOutputProtocol + ) self.push(module: assembly.makeModule()) } diff --git a/Stepic/Sources/Modules/CourseInfo/InputOutput/CourseInfoInputProtocol.swift b/Stepic/Sources/Modules/CourseInfo/InputOutput/CourseInfoInputProtocol.swift new file mode 100644 index 0000000000..9e22a4965b --- /dev/null +++ b/Stepic/Sources/Modules/CourseInfo/InputOutput/CourseInfoInputProtocol.swift @@ -0,0 +1,3 @@ +import Foundation + +protocol CourseInfoInputProtocol: AnyObject {} diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContentLanguageSwitch/ContentLanguageSwitchAssembly.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContentLanguageSwitch/ContentLanguageSwitchAssembly.swift index f5c6d9458e..34dcade179 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContentLanguageSwitch/ContentLanguageSwitchAssembly.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContentLanguageSwitch/ContentLanguageSwitchAssembly.swift @@ -8,7 +8,8 @@ final class ContentLanguageSwitchAssembly: Assembly { let presenter = ContentLanguageSwitchPresenter() let interactor = ContentLanguageSwitchInteractor( presenter: presenter, - provider: provider + provider: provider, + analytics: StepikAnalytics.shared ) let viewController = ContentLanguageSwitchViewController( interactor: interactor diff --git a/Stepic/Sources/Modules/ExploreSubmodules/ContentLanguageSwitch/ContentLanguageSwitchInteractor.swift b/Stepic/Sources/Modules/ExploreSubmodules/ContentLanguageSwitch/ContentLanguageSwitchInteractor.swift index 443923ef86..a9560c0959 100644 --- a/Stepic/Sources/Modules/ExploreSubmodules/ContentLanguageSwitch/ContentLanguageSwitchInteractor.swift +++ b/Stepic/Sources/Modules/ExploreSubmodules/ContentLanguageSwitch/ContentLanguageSwitchInteractor.swift @@ -10,14 +10,18 @@ final class ContentLanguageSwitchInteractor: ContentLanguageSwitchInteractorProt private let presenter: ContentLanguageSwitchPresenterProtocol private let provider: ContentLanguageSwitchProviderProtocol + private let analytics: Analytics + private var currentAvailableContentLanguages: [(UniqueIdentifierType, ContentLanguage)] = [] init( presenter: ContentLanguageSwitchPresenterProtocol, - provider: ContentLanguageSwitchProviderProtocol + provider: ContentLanguageSwitchProviderProtocol, + analytics: Analytics ) { self.presenter = presenter self.provider = provider + self.analytics = analytics } func doLanguagesListPresentation(request: ContentLanguageSwitch.LanguagesLoad.Request) { @@ -56,6 +60,8 @@ final class ContentLanguageSwitchInteractor: ContentLanguageSwitchInteractorProt fatalError("Request contains invalid data") } + self.analytics.send(.contentLanguageChanged(selectedLanguage, source: .catalog)) + self.provider.setGlobalContentLanguage(selectedLanguage) self.presenter.presentLanguageChange( response: ContentLanguageSwitch.LanguageSelection.Response( diff --git a/Stepic/Sources/Modules/Lesson/InputOutput/LessonOutputProtocol.swift b/Stepic/Sources/Modules/Lesson/InputOutput/LessonOutputProtocol.swift new file mode 100644 index 0000000000..c031f90a45 --- /dev/null +++ b/Stepic/Sources/Modules/Lesson/InputOutput/LessonOutputProtocol.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol LessonOutputProtocol: AnyObject { + func handleLessonDidRequestBuyCourse() +} diff --git a/Stepic/Sources/Modules/Lesson/LessonAssembly.swift b/Stepic/Sources/Modules/Lesson/LessonAssembly.swift index 5794a270e8..323db3457a 100644 --- a/Stepic/Sources/Modules/Lesson/LessonAssembly.swift +++ b/Stepic/Sources/Modules/Lesson/LessonAssembly.swift @@ -4,9 +4,16 @@ final class LessonAssembly: Assembly { private var initialContext: LessonDataFlow.Context private var startStep: LessonDataFlow.StartStep? - init(initialContext: LessonDataFlow.Context, startStep: LessonDataFlow.StartStep? = nil) { + private weak var moduleOutput: LessonOutputProtocol? + + init( + initialContext: LessonDataFlow.Context, + startStep: LessonDataFlow.StartStep? = nil, + moduleOutput: LessonOutputProtocol? + ) { self.initialContext = initialContext self.startStep = startStep + self.moduleOutput = moduleOutput } func makeModule() -> UIViewController { @@ -39,7 +46,9 @@ final class LessonAssembly: Assembly { assignmentsPersistenceService: AssignmentsPersistenceService(), progressesPersistenceService: ProgressesPersistenceService(), progressesNetworkService: ProgressesNetworkService(progressesAPI: ProgressesAPI()), - viewsNetworkService: ViewsNetworkService(viewsAPI: ViewsAPI()) + viewsNetworkService: ViewsNetworkService(viewsAPI: ViewsAPI()), + coursesPersistenceService: CoursesPersistenceService(), + coursesNetworkService: CoursesNetworkService(coursesAPI: CoursesAPI()) ) let presenter = LessonPresenter(urlFactory: StepikURLFactory()) let interactor = LessonInteractor( @@ -58,6 +67,7 @@ final class LessonAssembly: Assembly { viewController.hidesBottomBarWhenPushed = true presenter.viewController = viewController + interactor.moduleOutput = self.moduleOutput return viewController } diff --git a/Stepic/Sources/Modules/Lesson/LessonDataFlow.swift b/Stepic/Sources/Modules/Lesson/LessonDataFlow.swift index 1e47620353..1bc6d476de 100644 --- a/Stepic/Sources/Modules/Lesson/LessonDataFlow.swift +++ b/Stepic/Sources/Modules/Lesson/LessonDataFlow.swift @@ -129,6 +129,17 @@ enum LessonDataFlow { } } + /// Present modal with finished demo access info + enum UnitNavigationFinishedDemoAccessPresentation { + struct Response { + let section: Section + } + + struct ViewModel { + let sectionID: Section.IdType + } + } + /// Present new lesson module enum LessonModulePresentation { struct Response { @@ -243,6 +254,11 @@ enum LessonDataFlow { } } + /// Do buy course action + enum BuyCourseAction { + struct Request {} + } + /// Handle HUD enum BlockingWaitingIndicatorUpdate { struct Response { diff --git a/Stepic/Sources/Modules/Lesson/LessonInteractor.swift b/Stepic/Sources/Modules/Lesson/LessonInteractor.swift index c9e093aea0..0c9111d435 100644 --- a/Stepic/Sources/Modules/Lesson/LessonInteractor.swift +++ b/Stepic/Sources/Modules/Lesson/LessonInteractor.swift @@ -5,9 +5,12 @@ protocol LessonInteractorProtocol { func doLessonLoad(request: LessonDataFlow.LessonLoad.Request) func doEditStepPresentation(request: LessonDataFlow.EditStepPresentation.Request) func doSubmissionsPresentation(request: LessonDataFlow.SubmissionsPresentation.Request) + func doBuyCourse(request: LessonDataFlow.BuyCourseAction.Request) } final class LessonInteractor: LessonInteractorProtocol { + weak var moduleOutput: LessonOutputProtocol? + private let presenter: LessonPresenterProtocol private let provider: LessonProviderProtocol private let unitNavigationService: UnitNavigationServiceProtocol @@ -74,6 +77,10 @@ final class LessonInteractor: LessonInteractorProtocol { self.presenter.presentSubmissions(response: .init(stepID: stepID, isTeacher: lesson.canEdit)) } + func doBuyCourse(request: LessonDataFlow.BuyCourseAction.Request) { + self.moduleOutput?.handleLessonDidRequestBuyCourse() + } + // MARK: Private API private func refreshLesson( @@ -297,6 +304,7 @@ final class LessonInteractor: LessonInteractorProtocol { extension LessonInteractor: StepOutputProtocol { private static let autoplayDelay: TimeInterval = 0.33 + private static let unitNavigationDelay: TimeInterval = 0.5 func handleStepView(id: Step.IdType) { let assignmentID = self.assignmentsForCurrentSteps[id] @@ -327,13 +335,19 @@ extension LessonInteractor: StepOutputProtocol { return } - let didPresentUnreachableState = self.presentUnreachableUnitNavigationState(targetUnit: unit) - - if !didPresentUnreachableState { - self.presenter.presentWaitingState(response: .init(shouldDismiss: false)) - self.didLoadFromCache = false + self.presenter.presentWaitingState(response: .init(shouldDismiss: false)) - self.refreshLesson(context: .unit(id: unit.id)).cauterize() + firstly { + after(seconds: Self.unitNavigationDelay) + }.then { + self.presentUnreachableUnitNavigationState(targetUnit: unit) + }.done { didPresentUnreachableState in + if didPresentUnreachableState { + self.presenter.presentWaitingState(response: .init(shouldDismiss: true)) + } else { + self.didLoadFromCache = false + self.refreshLesson(context: .unit(id: unit.id)).cauterize() + } } } @@ -397,17 +411,23 @@ extension LessonInteractor: StepOutputProtocol { return } - let didPresentUnreachableState = self.presentUnreachableUnitNavigationState(targetUnit: unit) - - if !didPresentUnreachableState { - self.presenter.presentWaitingState(response: .init(shouldDismiss: false)) - self.didLoadFromCache = false + self.presenter.presentWaitingState(response: .init(shouldDismiss: false)) - self.refreshLesson(context: .unit(id: unit.id)).done { - if autoplayNext { - self.autoplayCurrentStep() - } - }.cauterize() + firstly { + after(seconds: Self.unitNavigationDelay) + }.then { + self.presentUnreachableUnitNavigationState(targetUnit: unit) + }.done { didPresentUnreachableState in + if didPresentUnreachableState { + self.presenter.presentWaitingState(response: .init(shouldDismiss: true)) + } else { + self.didLoadFromCache = false + self.refreshLesson(context: .unit(id: unit.id)).done { + if autoplayNext { + self.autoplayCurrentStep() + } + }.cauterize() + } } } @@ -433,29 +453,33 @@ extension LessonInteractor: StepOutputProtocol { } } - private func presentUnreachableUnitNavigationState(targetUnit: Unit) -> Bool { - guard let currentSection = self.currentUnit?.section, + private func presentUnreachableUnitNavigationState(targetUnit: Unit) -> Guarantee { + guard let currentLesson = self.currentLesson, + let currentSection = self.currentUnit?.section, let targetSection = targetUnit.section else { - return false + return .value(false) } if targetSection.testSectionAction != nil { - return false + return .value(false) } if targetSection.isReachable && !targetSection.isExam { - return false + return .value(false) } - if !targetSection.isRequirementSatisfied, - let requiredSectionID = targetSection.requiredSectionID { - self.presentRequirementNotSatisfiedUnitNavigationState( - currentSection: currentSection, - targetSection: targetSection, - requiredSectionID: requiredSectionID - ).catch { _ in - self.presenter.presentUnitNavigationUnreachableState(response: .init(targetSection: targetSection)) + if !targetSection.isRequirementSatisfied, let requiredSectionID = targetSection.requiredSectionID { + return Guarantee { seal in + self.presentRequirementNotSatisfiedUnitNavigationState( + currentSection: currentSection, + targetSection: targetSection, + requiredSectionID: requiredSectionID + ).done { _ in + seal(true) + }.catch { _ in + self.presenter.presentUnitNavigationUnreachableState(response: .init(targetSection: targetSection)) + seal(true) + } } - return true } if let beginDate = targetSection.beginDate, Date() < beginDate { @@ -466,7 +490,7 @@ extension LessonInteractor: StepOutputProtocol { dateSource: .beginDate ) ) - return true + return .value(true) } if let endDate = targetSection.endDate, Date() > endDate { @@ -477,17 +501,21 @@ extension LessonInteractor: StepOutputProtocol { dateSource: .endDate ) ) - return true + return .value(true) } if targetSection.isExam { self.presenter.presentUnitNavigationExamState( response: .init(currentSection: currentSection, targetSection: targetSection) ) - return true + return .value(true) } - return false + return self.presentUnitNavigationFinishedDemoAccessState( + currentLesson: currentLesson, + currentSection: currentSection, + targetUnit: targetUnit + ) } private func presentRequirementNotSatisfiedUnitNavigationState( @@ -523,6 +551,47 @@ extension LessonInteractor: StepOutputProtocol { ) } } + + private func presentUnitNavigationFinishedDemoAccessState( + currentLesson: Lesson, + currentSection: Section, + targetUnit: Unit + ) -> Guarantee { + guard currentLesson.canLearnLesson else { + return .value(false) + } + + return Guarantee { seal in + firstly { () -> Promise in + if let course = currentSection.course { + return .value(course) + } else { + return self.provider.fetchCourseFromCacheOrNetwork(id: currentSection.courseId).compactMap { $0 } + } + }.then { course -> Promise in + guard !course.enrolled && course.isPaid else { + throw Error.fetchFailed + } + + if let lesson = targetUnit.lesson { + return .value(lesson) + } else { + return self.provider.fetchLessonFromCacheOrNetwork(id: targetUnit.lessonId).compactMap { $0 } + } + }.done { targetLesson in + if !targetLesson.canLearnLesson { + self.presenter.presentUnitNavigationFinishedDemoAccessState( + response: .init(section: currentSection) + ) + seal(true) + } else { + seal(false) + } + }.catch { _ in + seal(false) + } + } + } } // MARK: - LessonInteractor: EditStepOutputProtocol - diff --git a/Stepic/Sources/Modules/Lesson/LessonPresenter.swift b/Stepic/Sources/Modules/Lesson/LessonPresenter.swift index 137c35e6c4..ae498cb88d 100644 --- a/Stepic/Sources/Modules/Lesson/LessonPresenter.swift +++ b/Stepic/Sources/Modules/Lesson/LessonPresenter.swift @@ -21,6 +21,9 @@ protocol LessonPresenterProtocol { func presentUnitNavigationClosedByDateState( response: LessonDataFlow.UnitNavigationClosedByDatePresentation.Response ) + func presentUnitNavigationFinishedDemoAccessState( + response: LessonDataFlow.UnitNavigationFinishedDemoAccessPresentation.Response + ) } final class LessonPresenter: LessonPresenterProtocol { @@ -215,6 +218,14 @@ final class LessonPresenter: LessonPresenterProtocol { self.viewController?.displayUnitNavigationClosedByDateState(viewModel: .init(title: title, message: message)) } + func presentUnitNavigationFinishedDemoAccessState( + response: LessonDataFlow.UnitNavigationFinishedDemoAccessPresentation.Response + ) { + self.viewController?.displayUnitNavigationFinishedDemoAccessState( + viewModel: .init(sectionID: response.section.id) + ) + } + // MAKE: Private API private func makeViewModel( diff --git a/Stepic/Sources/Modules/Lesson/LessonProvider.swift b/Stepic/Sources/Modules/Lesson/LessonProvider.swift index e2e9edcddf..1915eca2eb 100644 --- a/Stepic/Sources/Modules/Lesson/LessonProvider.swift +++ b/Stepic/Sources/Modules/Lesson/LessonProvider.swift @@ -10,20 +10,41 @@ protocol LessonProviderProtocol { func fetchProgresses(ids: [Progress.IdType], dataSourceType: DataSourceType) -> Promise<[Progress]> func fetchProgresses(ids: [Progress.IdType]) -> Promise> func fetchSection(id: Section.IdType, dataSourceType: DataSourceType) -> Promise + func fetchCourse(id: Course.IdType, dataSourceType: DataSourceType) -> Promise func createView(stepID: Step.IdType, assignmentID: Assignment.IdType?) -> Promise } extension LessonProviderProtocol { + func fetchLessonFromCacheOrNetwork(id: Lesson.IdType) -> Promise { + self.fetchLesson(id: id, dataSourceType: .cache).then { cachedLessonOrNil -> Promise in + if let cachedLesson = cachedLessonOrNil { + return .value(cachedLesson) + } else { + return self.fetchLesson(id: id, dataSourceType: .remote) + } + } + } + func fetchSectionFromCacheOrNetwork(id: Section.IdType) -> Promise { - self.fetchSection(id: id, dataSourceType: .cache).then { cachedSection -> Promise in - if let cachedSection = cachedSection { + self.fetchSection(id: id, dataSourceType: .cache).then { cachedSectionOrNil -> Promise in + if let cachedSection = cachedSectionOrNil { return .value(cachedSection) } else { return self.fetchSection(id: id, dataSourceType: .remote) } } } + + func fetchCourseFromCacheOrNetwork(id: Course.IdType) -> Promise { + self.fetchCourse(id: id, dataSourceType: .cache).then { cachedCourseOrNil -> Promise in + if let cachedCourse = cachedCourseOrNil { + return .value(cachedCourse) + } else { + return self.fetchCourse(id: id, dataSourceType: .remote) + } + } + } } final class LessonProvider: LessonProviderProtocol { @@ -40,6 +61,8 @@ final class LessonProvider: LessonProviderProtocol { private let progressesPersistenceService: ProgressesPersistenceServiceProtocol private let progressesNetworkService: ProgressesNetworkServiceProtocol private let viewsNetworkService: ViewsNetworkServiceProtocol + private let coursesPersistenceService: CoursesPersistenceServiceProtocol + private let coursesNetworkService: CoursesNetworkServiceProtocol init( lessonsPersistenceService: LessonsPersistenceServiceProtocol, @@ -54,7 +77,9 @@ final class LessonProvider: LessonProviderProtocol { assignmentsPersistenceService: AssignmentsPersistenceServiceProtocol, progressesPersistenceService: ProgressesPersistenceServiceProtocol, progressesNetworkService: ProgressesNetworkServiceProtocol, - viewsNetworkService: ViewsNetworkServiceProtocol + viewsNetworkService: ViewsNetworkServiceProtocol, + coursesPersistenceService: CoursesPersistenceServiceProtocol, + coursesNetworkService: CoursesNetworkServiceProtocol ) { self.lessonsPersistenceService = lessonsPersistenceService self.lessonsNetworkService = lessonsNetworkService @@ -69,6 +94,8 @@ final class LessonProvider: LessonProviderProtocol { self.progressesPersistenceService = progressesPersistenceService self.progressesNetworkService = progressesNetworkService self.viewsNetworkService = viewsNetworkService + self.coursesPersistenceService = coursesPersistenceService + self.coursesNetworkService = coursesNetworkService } // MARK: Public API @@ -239,6 +266,15 @@ final class LessonProvider: LessonProviderProtocol { } } + func fetchCourse(id: Course.IdType, dataSourceType: DataSourceType) -> Promise { + switch dataSourceType { + case .remote: + return self.coursesNetworkService.fetch(id: id) + case .cache: + return self.coursesPersistenceService.fetch(id: id) + } + } + func createView(stepID: Step.IdType, assignmentID: Assignment.IdType?) -> Promise { Promise { seal in self.viewsNetworkService.create(step: stepID, assignment: assignmentID).done { diff --git a/Stepic/Sources/Modules/Lesson/LessonViewController.swift b/Stepic/Sources/Modules/Lesson/LessonViewController.swift index 97a51bae17..9928b5782f 100644 --- a/Stepic/Sources/Modules/Lesson/LessonViewController.swift +++ b/Stepic/Sources/Modules/Lesson/LessonViewController.swift @@ -1,5 +1,6 @@ import EasyTipView import Pageboy +import PanModal import SnapKit import SVProgressHUD import Tabman @@ -29,6 +30,9 @@ protocol LessonViewControllerProtocol: AnyObject { func displayUnitNavigationClosedByDateState( viewModel: LessonDataFlow.UnitNavigationClosedByDatePresentation.ViewModel ) + func displayUnitNavigationFinishedDemoAccessState( + viewModel: LessonDataFlow.UnitNavigationFinishedDemoAccessPresentation.ViewModel + ) } // MARK: - LessonViewController: TabmanViewController, ControllerWithStepikPlaceholder - @@ -643,6 +647,20 @@ extension LessonViewController: LessonViewControllerProtocol { ) self.present(alert, animated: true) } + + func displayUnitNavigationFinishedDemoAccessState( + viewModel: LessonDataFlow.UnitNavigationFinishedDemoAccessPresentation.ViewModel + ) { + let assembly = LessonFinishedDemoPanModalAssembly( + sectionID: viewModel.sectionID, + output: self + ) + let viewController = assembly.makeModule() + + if let panModalPresentableViewController = viewController as? UIViewController & PanModalPresentable { + self.presentPanModalWithCustomModalPresentationStyle(panModalPresentableViewController) + } + } } // MARK: - LessonViewController: EasyTipViewDelegate - @@ -655,3 +673,11 @@ extension LessonViewController: EasyTipViewDelegate { self.tooltipView = nil } } + +extension LessonViewController: LessonFinishedDemoPanModalOutputProtocol { + func handleLessonFinishedDemoPanModalMainAction() { + self.dismiss(animated: true) { [weak self] in + self?.interactor.doBuyCourse(request: .init()) + } + } +} diff --git a/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/InputOutput/LessonFinishedDemoPanModalOutputProtocol.swift b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/InputOutput/LessonFinishedDemoPanModalOutputProtocol.swift new file mode 100644 index 0000000000..b2fe5165ba --- /dev/null +++ b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/InputOutput/LessonFinishedDemoPanModalOutputProtocol.swift @@ -0,0 +1,5 @@ +import Foundation + +protocol LessonFinishedDemoPanModalOutputProtocol: AnyObject { + func handleLessonFinishedDemoPanModalMainAction() +} diff --git a/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalAssembly.swift b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalAssembly.swift new file mode 100644 index 0000000000..23c8cc5564 --- /dev/null +++ b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalAssembly.swift @@ -0,0 +1,36 @@ +import UIKit + +final class LessonFinishedDemoPanModalAssembly: Assembly { + private let sectionID: Section.IdType + + private weak var moduleOutput: LessonFinishedDemoPanModalOutputProtocol? + + init( + sectionID: Section.IdType, + output: LessonFinishedDemoPanModalOutputProtocol? = nil + ) { + self.sectionID = sectionID + self.moduleOutput = output + } + + func makeModule() -> UIViewController { + let provider = LessonFinishedDemoPanModalProvider( + sectionsPersistenceService: SectionsPersistenceService(), + sectionsNetworkService: SectionsNetworkService(sectionsAPI: SectionsAPI()), + coursesPersistenceService: CoursesPersistenceService(), + coursesNetworkService: CoursesNetworkService(coursesAPI: CoursesAPI()) + ) + let presenter = LessonFinishedDemoPanModalPresenter() + let interactor = LessonFinishedDemoPanModalInteractor( + presenter: presenter, + provider: provider, + sectionID: self.sectionID + ) + let viewController = LessonFinishedDemoPanModalViewController(interactor: interactor) + + presenter.viewController = viewController + interactor.moduleOutput = self.moduleOutput + + return viewController + } +} diff --git a/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalDataFlow.swift b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalDataFlow.swift new file mode 100644 index 0000000000..79d64d8ff2 --- /dev/null +++ b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalDataFlow.swift @@ -0,0 +1,22 @@ +import Foundation + +enum LessonFinishedDemoPanModal { + enum ModalLoad { + struct Request {} + + struct Response { + let course: Course + let section: Section + } + + struct ViewModel { + let title: String + let subtitle: String + let actionButtonTitle: String + } + } + + enum MainModalAction { + struct Request {} + } +} diff --git a/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalInteractor.swift b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalInteractor.swift new file mode 100644 index 0000000000..82df003e46 --- /dev/null +++ b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalInteractor.swift @@ -0,0 +1,57 @@ +import Foundation +import PromiseKit + +protocol LessonFinishedDemoPanModalInteractorProtocol { + func doModalLoad(request: LessonFinishedDemoPanModal.ModalLoad.Request) + func doModalMainAction(request: LessonFinishedDemoPanModal.MainModalAction.Request) +} + +final class LessonFinishedDemoPanModalInteractor: LessonFinishedDemoPanModalInteractorProtocol { + weak var moduleOutput: LessonFinishedDemoPanModalOutputProtocol? + + private let presenter: LessonFinishedDemoPanModalPresenterProtocol + private let provider: LessonFinishedDemoPanModalProviderProtocol + + private let sectionID: Section.IdType + + init( + presenter: LessonFinishedDemoPanModalPresenterProtocol, + provider: LessonFinishedDemoPanModalProviderProtocol, + sectionID: Section.IdType + ) { + self.presenter = presenter + self.provider = provider + self.sectionID = sectionID + } + + func doModalLoad(request: LessonFinishedDemoPanModal.ModalLoad.Request) { + self.provider + .fetchSection(id: self.sectionID) + .compactMap { $0 } + .then { section -> Promise<(Section, Course)> in + if let course = section.course { + return .value((section, course)) + } else { + return self.provider + .fetchCourse(id: section.courseId) + .compactMap { $0 } + .then { course -> Promise<(Section, Course)> in + section.course = course + CoreDataHelper.shared.save() + + return .value((section, course)) + } + } + } + .done { section, course in + self.presenter.presentModal(response: .init(course: course, section: section)) + } + .catch { error in + print("LessonFinishedDemoPanModalInteractor :: failed load data with error = \(error)") + } + } + + func doModalMainAction(request: LessonFinishedDemoPanModal.MainModalAction.Request) { + self.moduleOutput?.handleLessonFinishedDemoPanModalMainAction() + } +} diff --git a/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalPresenter.swift b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalPresenter.swift new file mode 100644 index 0000000000..ba37f0d281 --- /dev/null +++ b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalPresenter.swift @@ -0,0 +1,29 @@ +import UIKit + +protocol LessonFinishedDemoPanModalPresenterProtocol { + func presentModal(response: LessonFinishedDemoPanModal.ModalLoad.Response) +} + +final class LessonFinishedDemoPanModalPresenter: LessonFinishedDemoPanModalPresenterProtocol { + weak var viewController: LessonFinishedDemoPanModalViewControllerProtocol? + + func presentModal(response: LessonFinishedDemoPanModal.ModalLoad.Response) { + let title = String( + format: NSLocalizedString("LessonFinishedDemoPanModalTitle", comment: ""), + arguments: [response.section.title] + ) + + let actionButtonTitle = String( + format: NSLocalizedString("WidgetButtonBuy", comment: ""), + arguments: [response.course.displayPrice ?? "N/A"] + ) + + self.viewController?.displayModal( + viewModel: .init( + title: title, + subtitle: NSLocalizedString("LessonFinishedDemoPanModalSubtitle", comment: ""), + actionButtonTitle: actionButtonTitle + ) + ) + } +} diff --git a/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalProvider.swift b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalProvider.swift new file mode 100644 index 0000000000..49fbd907c0 --- /dev/null +++ b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalProvider.swift @@ -0,0 +1,47 @@ +import Foundation +import PromiseKit + +protocol LessonFinishedDemoPanModalProviderProtocol { + func fetchCourse(id: Course.IdType) -> Promise + func fetchSection(id: Section.IdType) -> Promise +} + +final class LessonFinishedDemoPanModalProvider: LessonFinishedDemoPanModalProviderProtocol { + private let sectionsPersistenceService: SectionsPersistenceServiceProtocol + private let sectionsNetworkService: SectionsNetworkServiceProtocol + + private let coursesPersistenceService: CoursesPersistenceServiceProtocol + private let coursesNetworkService: CoursesNetworkServiceProtocol + + init( + sectionsPersistenceService: SectionsPersistenceServiceProtocol, + sectionsNetworkService: SectionsNetworkServiceProtocol, + coursesPersistenceService: CoursesPersistenceServiceProtocol, + coursesNetworkService: CoursesNetworkServiceProtocol + ) { + self.sectionsPersistenceService = sectionsPersistenceService + self.sectionsNetworkService = sectionsNetworkService + self.coursesPersistenceService = coursesPersistenceService + self.coursesNetworkService = coursesNetworkService + } + + func fetchSection(id: Section.IdType) -> Promise { + self.sectionsPersistenceService.fetch(id: id).then { cachedSectionOrNil -> Promise in + if let cachedSection = cachedSectionOrNil { + return .value(cachedSection) + } else { + return self.sectionsNetworkService.fetch(id: id) + } + } + } + + func fetchCourse(id: Course.IdType) -> Promise { + self.coursesPersistenceService.fetch(id: id).then { cachedCourseOrNil -> Promise in + if let cachedCourse = cachedCourseOrNil { + return .value(cachedCourse) + } else { + return self.coursesNetworkService.fetch(id: id) + } + } + } +} diff --git a/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalView.swift b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalView.swift new file mode 100644 index 0000000000..d0a51856c0 --- /dev/null +++ b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalView.swift @@ -0,0 +1,209 @@ +import SnapKit +import UIKit + +protocol LessonFinishedDemoPanModalViewDelegate: AnyObject { + func lessonFinishedDemoPanModalViewDidClickCloseButton(_ view: LessonFinishedDemoPanModalView) + func lessonFinishedDemoPanModalViewDidClickActionButton(_ view: LessonFinishedDemoPanModalView) +} + +extension LessonFinishedDemoPanModalView { + struct Appearance { + let backgroundColor = UIColor.stepikBackground + + let headerImageViewHeight: CGFloat = 136 + + let closeButtonWidthHeight: CGFloat = 32 + let closeButtonImageSize = CGSize(width: 24, height: 24) + let closeButtonInsets = LayoutInsets(top: 8, right: 8) + + let titleLabelFont = UIFont.systemFont(ofSize: 19, weight: .semibold) + let titleLabelTextColor = UIColor.stepikMaterialPrimaryText + + let subtitleLabelFont = Typography.bodyFont + let subtitleLabelTextColor = UIColor.stepikMaterialPrimaryText + + let actionButtonFont = Typography.bodyFont + let actionButtonCornerRadius: CGFloat = 8 + let actionButtonHeight: CGFloat = 44 + + let stackViewSpacing: CGFloat = 16 + let stackViewInsets = LayoutInsets(inset: 16) + } +} + +final class LessonFinishedDemoPanModalView: UIView { + weak var delegate: LessonFinishedDemoPanModalViewDelegate? + + let appearance: Appearance + + private lazy var headerImageView: UIImageView = { + let imageView = UIImageView(image: UIImage(named: "finished-demo-lesson-modal-header")) + imageView.contentMode = .scaleAspectFill + return imageView + }() + + private lazy var closeButton: SystemCloseButton = { + let appearance = SystemCloseButton.Appearance(imageSize: self.appearance.closeButtonImageSize) + let button = SystemCloseButton(appearance: appearance) + button.addTarget(self, action: #selector(self.closeButtonClicked), for: .touchUpInside) + return button + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.font = self.appearance.titleLabelFont + label.textColor = self.appearance.titleLabelTextColor + label.numberOfLines = 0 + return label + }() + + private lazy var subtitleLabel: UILabel = { + let label = UILabel() + label.font = self.appearance.subtitleLabelFont + label.textColor = self.appearance.subtitleLabelTextColor + label.numberOfLines = 0 + return label + }() + + private lazy var actionButton: UIButton = { + var appearance = NextStepButton.Appearance() + appearance.font = self.appearance.actionButtonFont + appearance.cornerRadius = self.appearance.actionButtonCornerRadius + + let button = NextStepButton(appearance: appearance) + button.addTarget(self, action: #selector(self.actionButtonClicked), for: .touchUpInside) + + return button + }() + + private lazy var contentStackView: UIStackView = { + let stackView = UIStackView() + stackView.axis = .vertical + stackView.spacing = self.appearance.stackViewSpacing + return stackView + }() + private lazy var contentStackViewContainerView = UIView() + + private lazy var scrollableStackView: ScrollableStackView = { + let scrollableStackView = ScrollableStackView(orientation: .vertical) + scrollableStackView.spacing = self.appearance.stackViewSpacing + return scrollableStackView + }() + + var title: String? { + didSet { + self.titleLabel.text = self.title + } + } + + var subtitle: String? { + didSet { + self.subtitleLabel.text = self.subtitle + } + } + + var actionButtonTitle: String? { + didSet { + self.actionButton.setTitle(self.actionButtonTitle, for: .normal) + } + } + + override var intrinsicContentSize: CGSize { + let height = self.appearance.headerImageViewHeight + + self.appearance.stackViewSpacing + + self.titleLabel.intrinsicContentSize.height + + self.appearance.stackViewSpacing + + self.subtitleLabel.intrinsicContentSize.height + + self.appearance.stackViewSpacing + + SeparatorView.Appearance().height + + self.appearance.stackViewSpacing + + self.appearance.actionButtonHeight + + self.appearance.stackViewSpacing + return CGSize( + width: UIView.noIntrinsicMetric, + height: height + ) + } + + init( + frame: CGRect = .zero, + appearance: Appearance = Appearance() + ) { + self.appearance = appearance + super.init(frame: frame) + + self.setupView() + self.addSubviews() + self.makeConstraints() + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc + private func closeButtonClicked() { + self.delegate?.lessonFinishedDemoPanModalViewDidClickCloseButton(self) + } + + @objc + private func actionButtonClicked() { + self.delegate?.lessonFinishedDemoPanModalViewDidClickActionButton(self) + } +} + +extension LessonFinishedDemoPanModalView: ProgrammaticallyInitializableViewProtocol { + func setupView() { + self.backgroundColor = self.appearance.backgroundColor + } + + func addSubviews() { + self.addSubview(self.scrollableStackView) + self.addSubview(self.closeButton) + + self.scrollableStackView.addArrangedView(self.headerImageView) + + self.contentStackViewContainerView.addSubview(self.contentStackView) + self.scrollableStackView.addArrangedView(self.contentStackViewContainerView) + + self.contentStackView.addArrangedSubview(self.titleLabel) + self.contentStackView.addArrangedSubview(self.subtitleLabel) + self.contentStackView.addArrangedSubview(SeparatorView()) + self.contentStackView.addArrangedSubview(self.actionButton) + } + + func makeConstraints() { + self.scrollableStackView.translatesAutoresizingMaskIntoConstraints = false + self.scrollableStackView.snp.makeConstraints { $0.edges.equalToSuperview() } + + self.headerImageView.translatesAutoresizingMaskIntoConstraints = false + self.headerImageView.snp.makeConstraints { $0.height.equalTo(self.appearance.headerImageViewHeight) } + + self.closeButton.translatesAutoresizingMaskIntoConstraints = false + self.closeButton.snp.makeConstraints { make in + make.width.height.equalTo(self.appearance.closeButtonWidthHeight) + make.top.equalToSuperview().offset(self.appearance.closeButtonInsets.top) + make.trailing.equalTo(self.safeAreaLayoutGuide).offset(-self.appearance.closeButtonInsets.right) + } + + self.actionButton.translatesAutoresizingMaskIntoConstraints = false + self.actionButton.snp.makeConstraints { $0.height.equalTo(self.appearance.actionButtonHeight) } + + self.contentStackView.translatesAutoresizingMaskIntoConstraints = false + self.contentStackView.snp.makeConstraints { make in + make.top.equalToSuperview() + make.leading + .equalTo(self.contentStackViewContainerView.safeAreaLayoutGuide) + .offset(self.appearance.stackViewInsets.left) + make.trailing + .equalTo(self.contentStackViewContainerView.safeAreaLayoutGuide) + .offset(-self.appearance.stackViewInsets.right) + make.bottom.equalToSuperview().offset(-self.appearance.stackViewInsets.bottom) + } + } +} + +extension LessonFinishedDemoPanModalView: PanModalScrollable { + var panScrollable: UIScrollView? { self.scrollableStackView.panScrollable } +} diff --git a/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalViewController.swift b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalViewController.swift new file mode 100644 index 0000000000..a1cdf75d66 --- /dev/null +++ b/Stepic/Sources/Modules/LessonPanModals/LessonFinishedDemoPanModal/LessonFinishedDemoPanModalViewController.swift @@ -0,0 +1,63 @@ +import PanModal +import UIKit + +protocol LessonFinishedDemoPanModalViewControllerProtocol: AnyObject { + func displayModal(viewModel: LessonFinishedDemoPanModal.ModalLoad.ViewModel) +} + +final class LessonFinishedDemoPanModalViewController: PanModalPresentableViewController { + private let interactor: LessonFinishedDemoPanModalInteractorProtocol + + private var hasLoadedData = false + + var lessonFinishedDemoPanModalView: LessonFinishedDemoPanModalView? { self.view as? LessonFinishedDemoPanModalView } + + override var panScrollable: UIScrollView? { self.lessonFinishedDemoPanModalView?.panScrollable } + + override var shortFormHeight: PanModalHeight { + if self.hasLoadedData && self.isShortFormEnabled, + let intrinsicContentSize = self.lessonFinishedDemoPanModalView?.intrinsicContentSize { + return .contentHeight(intrinsicContentSize.height) + } + return super.shortFormHeight + } + + init(interactor: LessonFinishedDemoPanModalInteractorProtocol) { + self.interactor = interactor + super.init() + } + + override func loadView() { + let view = LessonFinishedDemoPanModalView(frame: UIScreen.main.bounds) + self.view = view + view.delegate = self + } + + override func viewDidLoad() { + super.viewDidLoad() + self.interactor.doModalLoad(request: .init()) + } +} + +extension LessonFinishedDemoPanModalViewController: LessonFinishedDemoPanModalViewControllerProtocol { + func displayModal(viewModel: LessonFinishedDemoPanModal.ModalLoad.ViewModel) { + self.lessonFinishedDemoPanModalView?.title = viewModel.title + self.lessonFinishedDemoPanModalView?.subtitle = viewModel.subtitle + self.lessonFinishedDemoPanModalView?.actionButtonTitle = viewModel.actionButtonTitle + + self.hasLoadedData = true + + self.panModalSetNeedsLayoutUpdate() + self.panModalTransition(to: .shortForm) + } +} + +extension LessonFinishedDemoPanModalViewController: LessonFinishedDemoPanModalViewDelegate { + func lessonFinishedDemoPanModalViewDidClickCloseButton(_ view: LessonFinishedDemoPanModalView) { + self.dismiss(animated: true) + } + + func lessonFinishedDemoPanModalViewDidClickActionButton(_ view: LessonFinishedDemoPanModalView) { + self.interactor.doModalMainAction(request: .init()) + } +} diff --git a/Stepic/Sources/Modules/Quizzes/TableQuiz/TableQuizSelectColumns/TableQuizSelectColumnsViewController.swift b/Stepic/Sources/Modules/Quizzes/TableQuiz/TableQuizSelectColumns/TableQuizSelectColumnsViewController.swift index 2318fd5db0..c444f5b568 100644 --- a/Stepic/Sources/Modules/Quizzes/TableQuiz/TableQuizSelectColumns/TableQuizSelectColumnsViewController.swift +++ b/Stepic/Sources/Modules/Quizzes/TableQuiz/TableQuizSelectColumns/TableQuizSelectColumnsViewController.swift @@ -8,7 +8,7 @@ protocol TableQuizSelectColumnsViewControllerDelegate: AnyObject { ) } -final class TableQuizSelectColumnsViewController: UIViewController { +final class TableQuizSelectColumnsViewController: PanModalPresentableViewController { weak var moduleOutput: TableQuizSelectColumnsOutputProtocol? private let row: TableQuiz.Row @@ -20,7 +20,7 @@ final class TableQuizSelectColumnsViewController: UIViewController { var tableQuizSelectColumnsView: TableQuizSelectColumnsView? { self.view as? TableQuizSelectColumnsView } - private var isShortFormEnabled = true + override var panScrollable: UIScrollView? { self.tableQuizSelectColumnsView?.panScrollable } init( row: TableQuiz.Row, @@ -33,12 +33,7 @@ final class TableQuizSelectColumnsViewController: UIViewController { self.selectedColumnsIDs = selectedColumnsIDs self.isMultipleChoice = isMultipleChoice - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + super.init() } override func loadView() { @@ -49,34 +44,15 @@ final class TableQuizSelectColumnsViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - self.setupView() - } - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - - coordinator.animate( - alongsideTransition: { _ in - self.updateAdditionalSafeAreaInsets() - }, - completion: nil - ) - } - - private func setupView() { self.tableQuizSelectColumnsView?.prompt = self.isMultipleChoice ? NSLocalizedString("MultipleChoiceTableQuizPrompt", comment: "") : NSLocalizedString("SingleChoiceTableQuizPrompt", comment: "") self.tableQuizSelectColumnsView?.title = self.row.text self.tableQuizSelectColumnsView?.set(columns: self.columns, selectedColumnsIDs: self.selectedColumnsIDs) - self.updateAdditionalSafeAreaInsets() self.panModalSetNeedsLayoutUpdate() } - - private func updateAdditionalSafeAreaInsets() { - self.additionalSafeAreaInsets = UIApplication.shared.delegate?.window??.safeAreaInsets ?? .zero - } } extension TableQuizSelectColumnsViewController: TableQuizSelectColumnsViewDelegate { @@ -105,27 +81,6 @@ extension TableQuizSelectColumnsViewController: TableQuizSelectColumnsViewDelega } func tableQuizSelectColumnsViewDidClickClose(_ view: TableQuizSelectColumnsView) { - self.dismiss(animated: true, completion: nil) - } -} - -extension TableQuizSelectColumnsViewController: PanModalPresentable { - var panScrollable: UIScrollView? { self.tableQuizSelectColumnsView?.panScrollable } - - var shortFormHeight: PanModalHeight { - self.isShortFormEnabled - ? .contentHeight(floor(UIScreen.main.bounds.height / 3)) - : self.longFormHeight - } - - var anchorModalToLongForm: Bool { false } - - func willTransition(to state: PanModalPresentationController.PresentationState) { - guard self.isShortFormEnabled, case .longForm = state else { - return - } - - self.isShortFormEnabled = false - self.panModalSetNeedsLayoutUpdate() + self.dismiss(animated: true) } } diff --git a/Stepic/Sources/Modules/Settings/SettingsInteractor.swift b/Stepic/Sources/Modules/Settings/SettingsInteractor.swift index 3463caf122..03f21ec072 100644 --- a/Stepic/Sources/Modules/Settings/SettingsInteractor.swift +++ b/Stepic/Sources/Modules/Settings/SettingsInteractor.swift @@ -117,7 +117,9 @@ final class SettingsInteractor: SettingsInteractorProtocol { } func doContentLanguageSettingUpdate(request: Settings.ContentLanguageSettingUpdate.Request) { - self.provider.globalContentLanguage = ContentLanguage(languageString: request.setting.uniqueIdentifier) + let selectedContentLanguage = ContentLanguage(languageString: request.setting.uniqueIdentifier) + self.provider.globalContentLanguage = selectedContentLanguage + self.analytics.send(.contentLanguageChanged(selectedContentLanguage, source: .settings)) } func doStepFontSizeSettingPresentation(request: Settings.StepFontSizeSettingPresentation.Request) { diff --git a/Stepic/en.lproj/Localizable.strings b/Stepic/en.lproj/Localizable.strings index 40ee874d35..56a428d6ad 100644 --- a/Stepic/en.lproj/Localizable.strings +++ b/Stepic/en.lproj/Localizable.strings @@ -850,8 +850,8 @@ LessonUnitNavigationRequirementNotSatisfiedMessage = "To unlock \"%@\" module, y LessonUnitNavigationActionSyllabus = "Go to Syllabus"; LessonUnitNavigationRequirementNotSatisfiedActionContinue = "Continue Learning"; LessonUnitNavigationExamMessage = "Next module \"%@\" is exam. You can start it from the course syllabus."; -LessonUnitNavigationClosedByBeginDateMessage = "Next module \"%@\" will be available %@."; -LessonUnitNavigationClosedByEndDateMessage = "Access to the next \"%@\" module was denied %@."; +LessonUnitNavigationClosedByBeginDateMessage = "The next module \"%@\" will open %@."; +LessonUnitNavigationClosedByEndDateMessage = "The next module \"%@\" was closed %@."; DiscussionsButtonTitle = "Show Comments (%@)"; NoDiscussionsButtonTitle = "Leave a Comment"; DisabledDiscussionsButtonTitle = "Comments Disabled"; @@ -882,6 +882,10 @@ StepDisabledTeacherNoCoursePlanProTitle = "a PRO or paid course"; StepDisabledTeacherNoCoursePlanEnterpriseTitle = "an Enterprise course"; StepPosition = "Step %@"; +/* LessonFinishedDemoPanModal */ +LessonFinishedDemoPanModalTitle = "Congratulations! You have just finished the demo-module «%@»."; +LessonFinishedDemoPanModalSubtitle = "To view, the other lessons buy full access to the course."; + /* Video player */ VideoPlayerPlaybackFailedStateAlertTitle = "Video player error"; VideoPlayerPlaybackFailedStateAlertMessage = "Video playback error occurred, try again or close the player?"; diff --git a/Stepic/ru.lproj/Localizable.strings b/Stepic/ru.lproj/Localizable.strings index 308b85cc4c..fce08fb50d 100644 --- a/Stepic/ru.lproj/Localizable.strings +++ b/Stepic/ru.lproj/Localizable.strings @@ -883,6 +883,10 @@ StepDisabledTeacherNoCoursePlanProTitle = "платный или PRO- StepDisabledTeacherNoCoursePlanEnterpriseTitle = "Enterprise-курс"; StepPosition = "Шаг %@"; +/* LessonFinishedDemoPanModal */ +LessonFinishedDemoPanModalTitle = "Поздравляем! Вы закончили демо-модуль «%@»."; +LessonFinishedDemoPanModalSubtitle = "Для доступа к следующим урокам, купите курс."; + /* Video player */ VideoPlayerPlaybackFailedStateAlertTitle = "Ошибка видеоплеера"; VideoPlayerPlaybackFailedStateAlertMessage = "Возникла ошибка воспроизведения видео, попробовать снова или закрыть плеер?"; diff --git a/StepicTests/Info-Develop.plist b/StepicTests/Info-Develop.plist index 51f0ce3a93..8780965f9a 100644 --- a/StepicTests/Info-Develop.plist +++ b/StepicTests/Info-Develop.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.170-develop + 1.171-develop CFBundleSignature ???? CFBundleVersion - 324 + 326 diff --git a/StepicTests/Info-Production.plist b/StepicTests/Info-Production.plist index 4a4a5890a2..e77610f5e4 100644 --- a/StepicTests/Info-Production.plist +++ b/StepicTests/Info-Production.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.170 + 1.171 CFBundleSignature ???? CFBundleVersion - 324 + 326 diff --git a/StepicTests/Info-Release.plist b/StepicTests/Info-Release.plist index 209810577b..736490c54f 100644 --- a/StepicTests/Info-Release.plist +++ b/StepicTests/Info-Release.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.170-release + 1.171-release CFBundleSignature ???? CFBundleVersion - 324 + 326 diff --git a/StepicUITests/Info-Develop.plist b/StepicUITests/Info-Develop.plist index 8e8fe566e1..d5431e7bb0 100644 --- a/StepicUITests/Info-Develop.plist +++ b/StepicUITests/Info-Develop.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.170-develop + 1.171-develop CFBundleVersion - 324 + 326 diff --git a/StepicUITests/Info-Production.plist b/StepicUITests/Info-Production.plist index 4a83e6c8f6..95665b62bb 100644 --- a/StepicUITests/Info-Production.plist +++ b/StepicUITests/Info-Production.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.170 + 1.171 CFBundleVersion - 324 + 326 diff --git a/StepicUITests/Info-Release.plist b/StepicUITests/Info-Release.plist index ad27777f8b..1346e0fae2 100644 --- a/StepicUITests/Info-Release.plist +++ b/StepicUITests/Info-Release.plist @@ -15,8 +15,8 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.170-release + 1.171-release CFBundleVersion - 324 + 326 diff --git a/StepicWidget/Info-Develop.plist b/StepicWidget/Info-Develop.plist index 84a3c035a1..9ac44f856c 100644 --- a/StepicWidget/Info-Develop.plist +++ b/StepicWidget/Info-Develop.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.170-develop + 1.171-develop CFBundleVersion - 324 + 326 NSExtension NSExtensionPointIdentifier diff --git a/StepicWidget/Info-Production.plist b/StepicWidget/Info-Production.plist index 2876452521..0ae033da5a 100644 --- a/StepicWidget/Info-Production.plist +++ b/StepicWidget/Info-Production.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.170 + 1.171 CFBundleVersion - 324 + 326 NSExtension NSExtensionPointIdentifier diff --git a/StepicWidget/Info-Release.plist b/StepicWidget/Info-Release.plist index 51423bef01..aa6e84ffa7 100644 --- a/StepicWidget/Info-Release.plist +++ b/StepicWidget/Info-Release.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.170-release + 1.171-release CFBundleVersion - 324 + 326 NSExtension NSExtensionPointIdentifier diff --git a/StickerPackExtension/Info-Develop.plist b/StickerPackExtension/Info-Develop.plist index 32f1fa0947..93cb75755f 100644 --- a/StickerPackExtension/Info-Develop.plist +++ b/StickerPackExtension/Info-Develop.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.170-develop + 1.171-develop CFBundleVersion - 324 + 326 NSExtension NSExtensionPointIdentifier diff --git a/StickerPackExtension/Info-Production.plist b/StickerPackExtension/Info-Production.plist index cf0a76b55c..fbfdf3100a 100644 --- a/StickerPackExtension/Info-Production.plist +++ b/StickerPackExtension/Info-Production.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.170 + 1.171 CFBundleVersion - 324 + 326 NSExtension NSExtensionPointIdentifier diff --git a/StickerPackExtension/Info-Release.plist b/StickerPackExtension/Info-Release.plist index ae44453012..fd5d1d7fcc 100644 --- a/StickerPackExtension/Info-Release.plist +++ b/StickerPackExtension/Info-Release.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.170-release + 1.171-release CFBundleVersion - 324 + 326 NSExtension NSExtensionPointIdentifier diff --git a/fastlane/release-notes.txt b/fastlane/release-notes.txt index e33e690060..343ca8eba6 100644 --- a/fastlane/release-notes.txt +++ b/fastlane/release-notes.txt @@ -1,3 +1,2 @@ Что тестировать: -- Сниффинг трафика в debug версии APPS-3268 -- Сообщение при переходе на недоступный модуль APPS-3276 \ No newline at end of file +- Сообщение по окончанию демо уроков в платном курсе APPS-3279 \ No newline at end of file