diff --git a/LaunchDefaultsContainer.swift b/LaunchDefaultsContainer.swift index a4e06a7eed..714a7504d4 100644 --- a/LaunchDefaultsContainer.swift +++ b/LaunchDefaultsContainer.swift @@ -35,9 +35,7 @@ class LaunchDefaultsContainer { if let startVersion = defaults.value(forKey: startVersionKey) as? String { return startVersion } else { - let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0" - self.startVersion = version - return version + return initStartVersion() } } @@ -46,4 +44,9 @@ class LaunchDefaultsContainer { } } + @discardableResult func initStartVersion() -> String { + let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "0" + self.startVersion = version + return version + } } diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index daf0428576..ba9b24bfbc 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ 0800B81F1D06FDE5006C987E /* DiscussionProxiesAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0800B81E1D06FDE4006C987E /* DiscussionProxiesAPI.swift */; }; 0800B8221D07029B006C987E /* CommentsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0800B8211D07029B006C987E /* CommentsAPI.swift */; }; 0800B8251D0704B6006C987E /* ApiUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0800B8241D0704B6006C987E /* ApiUtil.swift */; }; - 08019A821DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */; }; + 08019A821DEED7E900691F0B /* NotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */; }; 0802177A1F54E91300186245 /* MenuBlocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080217791F54E91300186245 /* MenuBlocks.swift */; }; 0802177C1F54E91300186245 /* MenuBlocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080217791F54E91300186245 /* MenuBlocks.swift */; }; 0802177E1F54E91300186245 /* MenuBlocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080217791F54E91300186245 /* MenuBlocks.swift */; }; @@ -89,18 +89,6 @@ 081387E11D7AF7700092E05D /* StyledTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081387E01D7AF7700092E05D /* StyledTabBarViewController.swift */; }; 0813EEA61BFE5A5400DB4B83 /* Assignment+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0813EEA41BFE5A5400DB4B83 /* Assignment+CoreDataProperties.swift */; }; 0813EEA71BFE5A5400DB4B83 /* Assignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0813EEA51BFE5A5400DB4B83 /* Assignment.swift */; }; - 0815661B1F69C3910082B359 /* CustomSearchBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0815661A1F69C3910082B359 /* CustomSearchBar.xib */; }; - 0815661D1F69C3910082B359 /* CustomSearchBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0815661A1F69C3910082B359 /* CustomSearchBar.xib */; }; - 0815661F1F69C3910082B359 /* CustomSearchBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0815661A1F69C3910082B359 /* CustomSearchBar.xib */; }; - 081566201F69C3910082B359 /* CustomSearchBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0815661A1F69C3910082B359 /* CustomSearchBar.xib */; }; - 081566211F69C3910082B359 /* CustomSearchBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0815661A1F69C3910082B359 /* CustomSearchBar.xib */; }; - 081566221F69C3910082B359 /* CustomSearchBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0815661A1F69C3910082B359 /* CustomSearchBar.xib */; }; - 081566251F69C3AD0082B359 /* CustomSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081566241F69C3AD0082B359 /* CustomSearchBar.swift */; }; - 081566271F69C3AD0082B359 /* CustomSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081566241F69C3AD0082B359 /* CustomSearchBar.swift */; }; - 081566291F69C3AD0082B359 /* CustomSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081566241F69C3AD0082B359 /* CustomSearchBar.swift */; }; - 0815662A1F69C3AD0082B359 /* CustomSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081566241F69C3AD0082B359 /* CustomSearchBar.swift */; }; - 0815662B1F69C3AD0082B359 /* CustomSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081566241F69C3AD0082B359 /* CustomSearchBar.swift */; }; - 0815662C1F69C3AD0082B359 /* CustomSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081566241F69C3AD0082B359 /* CustomSearchBar.swift */; }; 08194EBA20B813AC00B34327 /* PersonalDeadlineCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08194EB920B813AC00B34327 /* PersonalDeadlineCounter.swift */; }; 0819856920BF242E00897BBA /* PersonalDeadlineEditScheduleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819856820BF242E00897BBA /* PersonalDeadlineEditScheduleViewController.swift */; }; 0819856C20BF273800897BBA /* PersonalDeadlineTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0819856A20BF273800897BBA /* PersonalDeadlineTableViewCell.swift */; }; @@ -139,12 +127,6 @@ 0829ED9520C865BB0018E6FF /* ContentLanguagePreferenceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0829ED9320C865BB0018E6FF /* ContentLanguagePreferenceTableViewCell.swift */; }; 0829ED9620C865BB0018E6FF /* ContentLanguagePreferenceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0829ED9420C865BB0018E6FF /* ContentLanguagePreferenceTableViewCell.xib */; }; 0829ED9920C8825E0018E6FF /* ExploreDefaultsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0829ED9820C8825E0018E6FF /* ExploreDefaultsContainer.swift */; }; - 082A88FE2046F0460079F038 /* NotificationPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */; }; - 082A88FF2046F0460079F038 /* NotificationPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */; }; - 082A89012046F0460079F038 /* NotificationPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */; }; - 082A89022046F0460079F038 /* NotificationPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */; }; - 082A89032046F0460079F038 /* NotificationPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */; }; - 082A89042046F0460079F038 /* NotificationPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */; }; 082B54CF20A9DD3000144817 /* PersonalDeadlinesModeSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082B54CE20A9DD3000144817 /* PersonalDeadlinesModeSelectionViewController.swift */; }; 082B54D120A9DD3C00144817 /* PersonalDeadlines.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 082B54D020A9DD3C00144817 /* PersonalDeadlines.storyboard */; }; 082B54D520AA290800144817 /* PersonalDeadlinesDefaultsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082B54D420AA290800144817 /* PersonalDeadlinesDefaultsContainer.swift */; }; @@ -168,11 +150,9 @@ 0834C4701E2CEC4E002F8516 /* MatchingQuizViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0834C46E1E2CEC4E002F8516 /* MatchingQuizViewController.swift */; }; 083540611CE5DD5000BDFEA5 /* NotificationReactionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083540601CE5DD5000BDFEA5 /* NotificationReactionHandler.swift */; }; 083540671CE5FC0E00BDFEA5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083540661CE5FC0E00BDFEA5 /* Notification.swift */; }; - 083554291CC8FD2D004C4E85 /* NotificationRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */; }; 083622D81CD1FA4800CD8915 /* StepTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083622D71CD1FA4800CD8915 /* StepTabView.swift */; }; 083622DC1CD1FA6700CD8915 /* StepTabView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 083622DB1CD1FA6700CD8915 /* StepTabView.xib */; }; 083749261DE5AE0400144C14 /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749251DE5AE0400144C14 /* Alerts.swift */; }; - 0837492A1DE5AF8A00144C14 /* StreaksAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */; }; 0837492D1DE5B07900144C14 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492C1DE5B07900144C14 /* AlertManager.swift */; }; 083749301DE5BBF700144C14 /* PreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492F1DE5BBF700144C14 /* PreferencesContainer.swift */; }; 083749341DE5BC2B00144C14 /* NotificationPreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749331DE5BC2A00144C14 /* NotificationPreferencesContainer.swift */; }; @@ -209,7 +189,6 @@ 084156951BCBFFBD006B8C73 /* Step+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084156911BCBFFBD006B8C73 /* Step+CoreDataProperties.swift */; }; 084156961BCBFFBD006B8C73 /* Step.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084156921BCBFFBD006B8C73 /* Step.swift */; }; 0841BDC71E082AE7008CE13E /* WatchDataHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0841BDC61E082AE7008CE13E /* WatchDataHelper.swift */; }; - 08421BC921764C9600E8A81B /* AuthAfterOnboardingSplitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08421BC821764C9600E8A81B /* AuthAfterOnboardingSplitTest.swift */; }; 08421BCB21764FC400E8A81B /* ActiveSplitTestsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08421BCA21764FC400E8A81B /* ActiveSplitTestsContainer.swift */; }; 084472061D05918E00197166 /* ChoiceQuizTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084472041D05918E00197166 /* ChoiceQuizTableViewCell.swift */; }; 084472081D05918E00197166 /* ChoiceQuizTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 084472051D05918E00197166 /* ChoiceQuizTableViewCell.xib */; }; @@ -251,6 +230,7 @@ 08484F18211AF4320006266F /* TextStoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08484EFD211AF4320006266F /* TextStoryView.swift */; }; 08484F1A211B5B750006266F /* Skeletonable+UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08484F19211B5B750006266F /* Skeletonable+UICollectionView.swift */; }; 08484F1C211B5DB20006266F /* StorySkeletonPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08484F1B211B5DB20006266F /* StorySkeletonPlaceholderView.xib */; }; + 0848BE8B218379A400762DC1 /* JoinCourseStringSplitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0848BE8A218379A400762DC1 /* JoinCourseStringSplitTest.swift */; }; 084BD9BB1E5368B600B1901E /* PickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084BD9BA1E5368B600B1901E /* PickerViewController.swift */; }; 084C658D1FDAD04C006A3E17 /* RemoteConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084C658C1FDAD04C006A3E17 /* RemoteConfig.swift */; }; 084C658F1FDAD04C006A3E17 /* RemoteConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 084C658C1FDAD04C006A3E17 /* RemoteConfig.swift */; }; @@ -626,12 +606,6 @@ 08CBA3591F5A2D9400302154 /* Profile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08CBA3531F5A2D9400302154 /* Profile.storyboard */; }; 08CBA35A1F5A2D9400302154 /* Profile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08CBA3531F5A2D9400302154 /* Profile.storyboard */; }; 08CBA35B1F5A2D9400302154 /* Profile.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08CBA3531F5A2D9400302154 /* Profile.storyboard */; }; - 08CCAEC1204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */; }; - 08CCAEC2204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */; }; - 08CCAEC4204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */; }; - 08CCAEC5204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */; }; - 08CCAEC6204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */; }; - 08CCAEC7204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */; }; 08CE16E01BFA5A80008511B7 /* DeviceInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CE16DF1BFA5A80008511B7 /* DeviceInfo.swift */; }; 08D035211D65A252003515C6 /* AnalyticsEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D035201D65A252003515C6 /* AnalyticsEvents.swift */; }; 08D035251D65B5E5003515C6 /* AnalyticsReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D035241D65B5E5003515C6 /* AnalyticsReporter.swift */; }; @@ -666,7 +640,6 @@ 08DB8CBB1D0BECF100A6D079 /* DiscussionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08DB8CB81D0BECF000A6D079 /* DiscussionTableViewCell.xib */; }; 08DDF90D20A9B2CC004ECC11 /* PersonalDeadlinesSuggestionWidgetView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08DDF90C20A9B2CC004ECC11 /* PersonalDeadlinesSuggestionWidgetView.xib */; }; 08DDF91020A9B2E9004ECC11 /* PersonalDeadlinesSuggestionWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DDF90F20A9B2E9004ECC11 /* PersonalDeadlinesSuggestionWidgetView.swift */; }; - 08DE94171B8C58AC00D278AB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE94161B8C58AC00D278AB /* AppDelegate.swift */; }; 08DE941C1B8C58AC00D278AB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941A1B8C58AC00D278AB /* Main.storyboard */; }; 08DE941E1B8C58AC00D278AB /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941D1B8C58AC00D278AB /* Images.xcassets */; }; 08DE94211B8C58AC00D278AB /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941F1B8C58AC00D278AB /* LaunchScreen.xib */; }; @@ -835,7 +808,6 @@ 08FEFC1C1F117257005CA0FB /* CodeSuggestionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FEFC1A1F117257005CA0FB /* CodeSuggestionTableViewCell.swift */; }; 08FEFC1E1F117257005CA0FB /* CodeSuggestionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08FEFC1B1F117257005CA0FB /* CodeSuggestionTableViewCell.xib */; }; 08FEFC211F127470005CA0FB /* AutocompleteWords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FEFC201F127470005CA0FB /* AutocompleteWords.swift */; }; - 08FF75301DF9B89000D656C4 /* StreaksStepikAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */; }; 0DD38F1F2CF8385C6D270649 /* ContentLanguageSwitchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C606EBF103D5682D5B26FF35 /* ContentLanguageSwitchInteractor.swift */; }; 164D9DD4DC279527C17BDCF7 /* ContentLanguageSwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 024D65DCBD4F8631B97D461D /* ContentLanguageSwitchView.swift */; }; 1B3BB05B1FE6DE48002DDD9A /* CourseInfoCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B3BB0501FE6DE46002DDD9A /* CourseInfoCollectionViewController.swift */; }; @@ -922,11 +894,12 @@ 2C0159E1213940850043DBFF /* LearningRouterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0159E0213940850043DBFF /* LearningRouterProtocol.swift */; }; 2C0159E3213940910043DBFF /* LearningRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0159E2213940910043DBFF /* LearningRouter.swift */; }; 2C0159E621394C9E0043DBFF /* TrainingCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0159E521394C9E0043DBFF /* TrainingCollectionViewController.swift */; }; + 2C0176C12188953700DDB9D0 /* NotificationAlertsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0176C02188953700DDB9D0 /* NotificationAlertsAnalytics.swift */; }; 2C03B2A41F0CD87600005383 /* StringHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C03B2A31F0CD87600005383 /* StringHelper.swift */; }; 2C093EF4211B3E3600162DD0 /* QuizViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081A7DA81C56625F00583728 /* QuizViewController.swift */; }; 2C093EF5211B418400162DD0 /* QuizPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1FC321F41E74500E14B46 /* QuizPresenter.swift */; }; 2C093EF6211B419300162DD0 /* QuizControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F555641C4FF22600C877E8 /* QuizControllerDelegate.swift */; }; - 2C093EF7211B41D500162DD0 /* StreaksNotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */; }; + 2C093EF7211B41D500162DD0 /* NotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */; }; 2C093EF8211B41F800162DD0 /* ReplyCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082E5E0D1F46379100F41426 /* ReplyCache.swift */; }; 2C093EF9211B423E00162DD0 /* AmplitudeAnalyticsEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081A14FE20D963090016364E /* AmplitudeAnalyticsEvents.swift */; }; 2C093EFA211B425F00162DD0 /* RoutingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082BE3B11E67686B006BC60F /* RoutingManager.swift */; }; @@ -951,9 +924,7 @@ 2C093F0D211B43AA00162DD0 /* MenuBlockTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DF78BB1F5EEFFE00AEEA85 /* MenuBlockTableViewCell.swift */; }; 2C093F0E211B43BD00162DD0 /* PlaceholderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C92669520B5C7CF00525AFC /* PlaceholderTableViewCell.swift */; }; 2C093F0F211B43DB00162DD0 /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749251DE5AE0400144C14 /* Alerts.swift */; }; - 2C093F10211B43FE00162DD0 /* StreaksStepikAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */; }; 2C093F11211B440E00162DD0 /* RateAppAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082799471E9B81AF008A3786 /* RateAppAlertManager.swift */; }; - 2C093F12211B441600162DD0 /* NotificationRequestAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */; }; 2C093F13211B442800162DD0 /* RateAppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0890560F1E98021000B8FE6A /* RateAppViewController.swift */; }; 2C093F14211B443300162DD0 /* MenuUIManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBA3001F57459800302154 /* MenuUIManager.swift */; }; 2C093F15211B444200162DD0 /* SwitchMenuBlockTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CBA3131F57562A00302154 /* SwitchMenuBlockTableViewCell.swift */; }; @@ -1195,7 +1166,7 @@ 2C1B61AD1F4C4AEF00236804 /* UpdatePreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6C1CD102DF00D94613 /* UpdatePreferencesContainer.swift */; }; 2C1B61AE1F4C4AEF00236804 /* VersionUpdateAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6F1CD111F200D94613 /* VersionUpdateAlertConstructor.swift */; }; 2C1B61AF1F4C4AEF00236804 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E711CD1173200D94613 /* Version.swift */; }; - 2C1B61B01F4C4AEF00236804 /* StreaksNotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */; }; + 2C1B61B01F4C4AEF00236804 /* NotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */; }; 2C1B61B21F4C4AEF00236804 /* Executable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6711CD80A9600B45652 /* Executable.swift */; }; 2C1B61B31F4C4AEF00236804 /* ExecutionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6741CD8106E00B45652 /* ExecutionQueue.swift */; }; 2C1B61B41F4C4AEF00236804 /* PersistentQueueRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0869F6D11CE216B600F8A6DB /* PersistentQueueRecoveryManager.swift */; }; @@ -1214,7 +1185,6 @@ 2C1B61C31F4C4AEF00236804 /* PersistentTaskManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08BC47081CD9FE10009A1D25 /* PersistentTaskManagerProtocol.swift */; }; 2C1B61C41F4C4AEF00236804 /* SearchQueriesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E8F9661F34DD2C008CF4A1 /* SearchQueriesPresenter.swift */; }; 2C1B61C51F4C4AEF00236804 /* RoutingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082BE3B11E67686B006BC60F /* RoutingManager.swift */; }; - 2C1B61C61F4C4AEF00236804 /* NotificationRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */; }; 2C1B61C71F4C4AEF00236804 /* NotificationAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0870392F1CD7413D00B6571B /* NotificationAlertConstructor.swift */; }; 2C1B61C81F4C4AEF00236804 /* DeviceDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AC214A1CE0DE9B00FBB9CD /* DeviceDefaults.swift */; }; 2C1B61CB1F4C4AEF00236804 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083540661CE5FC0E00BDFEA5 /* Notification.swift */; }; @@ -1302,9 +1272,7 @@ 2C1B62281F4C4AEF00236804 /* StepsControllerDeepLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085C4FF51D8C835600B27C95 /* StepsControllerDeepLinkRouter.swift */; }; 2C1B62291F4C4AEF00236804 /* UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6BB6A1DC8EB45006622EC /* UserActivity.swift */; }; 2C1B622A1F4C4AEF00236804 /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749251DE5AE0400144C14 /* Alerts.swift */; }; - 2C1B622B1F4C4AEF00236804 /* StreaksAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */; }; 2C1B622C1F4C4AEF00236804 /* QuizPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1FC321F41E74500E14B46 /* QuizPresenter.swift */; }; - 2C1B622D1F4C4AEF00236804 /* StreaksStepikAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */; }; 2C1B622E1F4C4AEF00236804 /* InputAccessoryBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9141F10EA690087D61B /* InputAccessoryBuilder.swift */; }; 2C1B622F1F4C4AEF00236804 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492C1DE5B07900144C14 /* AlertManager.swift */; }; 2C1B62301F4C4AEF00236804 /* CodeSuggestionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FEFC1A1F117257005CA0FB /* CodeSuggestionTableViewCell.swift */; }; @@ -1562,7 +1530,7 @@ 2C1B63BE1F4C590700236804 /* UpdatePreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6C1CD102DF00D94613 /* UpdatePreferencesContainer.swift */; }; 2C1B63BF1F4C590700236804 /* VersionUpdateAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6F1CD111F200D94613 /* VersionUpdateAlertConstructor.swift */; }; 2C1B63C01F4C590700236804 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E711CD1173200D94613 /* Version.swift */; }; - 2C1B63C11F4C590700236804 /* StreaksNotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */; }; + 2C1B63C11F4C590700236804 /* NotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */; }; 2C1B63C31F4C590700236804 /* Executable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6711CD80A9600B45652 /* Executable.swift */; }; 2C1B63C41F4C590700236804 /* ExecutionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6741CD8106E00B45652 /* ExecutionQueue.swift */; }; 2C1B63C51F4C590700236804 /* PersistentQueueRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0869F6D11CE216B600F8A6DB /* PersistentQueueRecoveryManager.swift */; }; @@ -1581,7 +1549,6 @@ 2C1B63D41F4C590700236804 /* PersistentTaskManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08BC47081CD9FE10009A1D25 /* PersistentTaskManagerProtocol.swift */; }; 2C1B63D51F4C590700236804 /* SearchQueriesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E8F9661F34DD2C008CF4A1 /* SearchQueriesPresenter.swift */; }; 2C1B63D61F4C590700236804 /* RoutingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082BE3B11E67686B006BC60F /* RoutingManager.swift */; }; - 2C1B63D71F4C590700236804 /* NotificationRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */; }; 2C1B63D81F4C590700236804 /* NotificationAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0870392F1CD7413D00B6571B /* NotificationAlertConstructor.swift */; }; 2C1B63D91F4C590700236804 /* DeviceDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AC214A1CE0DE9B00FBB9CD /* DeviceDefaults.swift */; }; 2C1B63DC1F4C590700236804 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083540661CE5FC0E00BDFEA5 /* Notification.swift */; }; @@ -1669,9 +1636,7 @@ 2C1B64391F4C590700236804 /* StepsControllerDeepLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085C4FF51D8C835600B27C95 /* StepsControllerDeepLinkRouter.swift */; }; 2C1B643A1F4C590700236804 /* UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6BB6A1DC8EB45006622EC /* UserActivity.swift */; }; 2C1B643B1F4C590700236804 /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749251DE5AE0400144C14 /* Alerts.swift */; }; - 2C1B643C1F4C590700236804 /* StreaksAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */; }; 2C1B643D1F4C590700236804 /* QuizPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1FC321F41E74500E14B46 /* QuizPresenter.swift */; }; - 2C1B643E1F4C590700236804 /* StreaksStepikAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */; }; 2C1B643F1F4C590700236804 /* InputAccessoryBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9141F10EA690087D61B /* InputAccessoryBuilder.swift */; }; 2C1B64401F4C590700236804 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492C1DE5B07900144C14 /* AlertManager.swift */; }; 2C1B64411F4C590700236804 /* CodeSuggestionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FEFC1A1F117257005CA0FB /* CodeSuggestionTableViewCell.swift */; }; @@ -1776,6 +1741,10 @@ 2C2972052147F5FD001502BD /* CourseTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A9F71D1FC38C9E00640F1F /* CourseTag.swift */; }; 2C2E2853210B5F6700EB6F47 /* VideoFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2365E321078CEF00C99742 /* VideoFileManager.swift */; }; 2C2EA36B212D5FEF002116C9 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2EA36A212D5FEF002116C9 /* Result.swift */; }; + 2C2F0BE72186EEB8007DCA0A /* StreakNotificationsRequestAlertDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2F0BE62186EEB8007DCA0A /* StreakNotificationsRequestAlertDataSource.swift */; }; + 2C2F0BE92186EF87007DCA0A /* CommonNotificationsRequestAlertDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2F0BE82186EF87007DCA0A /* CommonNotificationsRequestAlertDataSource.swift */; }; + 2C2F0BEC2186F0C3007DCA0A /* NotificationsRequestAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2F0BEB2186F0C3007DCA0A /* NotificationsRequestAlertPresenter.swift */; }; + 2C2F0BEE2186F196007DCA0A /* NotificationsRequestAlertDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2F0BED2186F196007DCA0A /* NotificationsRequestAlertDataSource.swift */; }; 2C2FD77A2107359E00609621 /* LessonsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD7782107359E00609621 /* LessonsTableViewController.swift */; }; 2C2FD77F210735E000609621 /* LessonTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C2FD77D210735E000609621 /* LessonTableViewCell.swift */; }; 2C2FD780210735E000609621 /* LessonTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C2FD77E210735E000609621 /* LessonTableViewCell.xib */; }; @@ -2216,6 +2185,7 @@ 2C77030E210F2C7B002E250D /* AuthSignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4F34012109F5C900E259FE /* AuthSignInViewController.swift */; }; 2C77030F210F2CF8002E250D /* APIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F261201E79DD0B00AC908B /* APIEndpoint.swift */; }; 2C770310210F2D1D002E250D /* ApiDataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0885F84D1BA837E200F2A188 /* ApiDataDownloader.swift */; }; + 2C79F61821873CD9004CC082 /* NotificationsRequestOnlySettingsAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C79F61721873CD9004CC082 /* NotificationsRequestOnlySettingsAlertPresenter.swift */; }; 2C7CCE282138276200A3AEA8 /* LearningTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7CCE272138276200A3AEA8 /* LearningTableViewController.swift */; }; 2C7CCE2B213827B400A3AEA8 /* LearningTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7CCE29213827B400A3AEA8 /* LearningTableViewCell.swift */; }; 2C7CCE2C213827B400A3AEA8 /* LearningTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C7CCE2A213827B400A3AEA8 /* LearningTableViewCell.xib */; }; @@ -2229,6 +2199,7 @@ 2C7FEE7F1FDFCEF200B2B4F1 /* OnboardingAnimatedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7FEE7E1FDFCEF200B2B4F1 /* OnboardingAnimatedView.swift */; }; 2C80C829201A504700ABB312 /* StepCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5DF1471FED2B71003B1177 /* StepCardView.swift */; }; 2C80C82A201A505E00ABB312 /* CongratulationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86BB7C05201953AF00063538 /* CongratulationViewController.swift */; }; + 2C82D51721830DD500C10805 /* NotificationsRegistrationServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C82D51621830DD500C10805 /* NotificationsRegistrationServiceProtocol.swift */; }; 2C8305EA20F38880003D7F9B /* Vertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8305E920F38880003D7F9B /* Vertex.swift */; }; 2C8305EC20F388B9003D7F9B /* Edge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8305EB20F388B9003D7F9B /* Edge.swift */; }; 2C8305F720F390F8003D7F9B /* GraphTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8305F620F390F8003D7F9B /* GraphTests.swift */; }; @@ -2445,7 +2416,7 @@ 2C89AA411F4C289900227C3B /* UpdatePreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6C1CD102DF00D94613 /* UpdatePreferencesContainer.swift */; }; 2C89AA421F4C289900227C3B /* VersionUpdateAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6F1CD111F200D94613 /* VersionUpdateAlertConstructor.swift */; }; 2C89AA431F4C289900227C3B /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E711CD1173200D94613 /* Version.swift */; }; - 2C89AA441F4C289900227C3B /* StreaksNotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */; }; + 2C89AA441F4C289900227C3B /* NotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */; }; 2C89AA461F4C289900227C3B /* Executable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6711CD80A9600B45652 /* Executable.swift */; }; 2C89AA471F4C289900227C3B /* ExecutionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6741CD8106E00B45652 /* ExecutionQueue.swift */; }; 2C89AA481F4C289900227C3B /* PersistentQueueRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0869F6D11CE216B600F8A6DB /* PersistentQueueRecoveryManager.swift */; }; @@ -2464,7 +2435,6 @@ 2C89AA571F4C289900227C3B /* PersistentTaskManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08BC47081CD9FE10009A1D25 /* PersistentTaskManagerProtocol.swift */; }; 2C89AA581F4C289900227C3B /* SearchQueriesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E8F9661F34DD2C008CF4A1 /* SearchQueriesPresenter.swift */; }; 2C89AA591F4C289900227C3B /* RoutingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082BE3B11E67686B006BC60F /* RoutingManager.swift */; }; - 2C89AA5A1F4C289900227C3B /* NotificationRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */; }; 2C89AA5B1F4C289900227C3B /* NotificationAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0870392F1CD7413D00B6571B /* NotificationAlertConstructor.swift */; }; 2C89AA5C1F4C289900227C3B /* DeviceDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AC214A1CE0DE9B00FBB9CD /* DeviceDefaults.swift */; }; 2C89AA5F1F4C289900227C3B /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083540661CE5FC0E00BDFEA5 /* Notification.swift */; }; @@ -2552,9 +2522,7 @@ 2C89AABC1F4C289900227C3B /* StepsControllerDeepLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085C4FF51D8C835600B27C95 /* StepsControllerDeepLinkRouter.swift */; }; 2C89AABD1F4C289900227C3B /* UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6BB6A1DC8EB45006622EC /* UserActivity.swift */; }; 2C89AABE1F4C289900227C3B /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749251DE5AE0400144C14 /* Alerts.swift */; }; - 2C89AABF1F4C289900227C3B /* StreaksAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */; }; 2C89AAC01F4C289900227C3B /* QuizPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1FC321F41E74500E14B46 /* QuizPresenter.swift */; }; - 2C89AAC11F4C289900227C3B /* StreaksStepikAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */; }; 2C89AAC21F4C289900227C3B /* InputAccessoryBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9141F10EA690087D61B /* InputAccessoryBuilder.swift */; }; 2C89AAC31F4C289900227C3B /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492C1DE5B07900144C14 /* AlertManager.swift */; }; 2C89AAC41F4C289900227C3B /* CodeSuggestionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FEFC1A1F117257005CA0FB /* CodeSuggestionTableViewCell.swift */; }; @@ -2655,7 +2623,6 @@ 2C9492C621130E6600552600 /* StepPlainObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9492C521130E6600552600 /* StepPlainObject.swift */; }; 2C9492C721130EA400552600 /* WebControllerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C9F7CF1C29AE8B00E544D0 /* WebControllerManager.swift */; }; 2C9492C821130F7C00552600 /* CourseServiceImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CCC59F72109B92300564D45 /* CourseServiceImpl.swift */; }; - 2C9492C921130FC100552600 /* NotificationRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */; }; 2C9492CA21130FD400552600 /* JSQWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083B164C1C2AF27700250B37 /* JSQWebViewController.swift */; }; 2C9492CD2113119100552600 /* StandartStepsAssemblyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9492CC2113119100552600 /* StandartStepsAssemblyProtocol.swift */; }; 2C9492CF2113119E00552600 /* StandartStepsAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C9492CE2113119E00552600 /* StandartStepsAssembly.swift */; }; @@ -2747,7 +2714,6 @@ 2C9499E820ECBF9B0049E9A8 /* NotificationRequestAlertContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C47A163206284B1003E87EC /* NotificationRequestAlertContext.swift */; }; 2C9499E920ECBFB20049E9A8 /* PlaybackStatusEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9C714F1E076B3E00EC8DA3 /* PlaybackStatusEntity.swift */; }; 2C9499EA20ECBFD90049E9A8 /* UIThreadHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08B5E2991BF509C900B875E6 /* UIThreadHelper.swift */; }; - 2C9499EC20ECBFED0049E9A8 /* NotificationPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */; }; 2C9499EE20ECC02A0049E9A8 /* TabsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F74601BD8159F0064AAEA /* TabsInfo.swift */; }; 2C9499EF20ECC0580049E9A8 /* Notification+FetchMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5F77C01F90F63B00E8E175 /* Notification+FetchMethods.swift */; }; 2C9499F020ECC0720049E9A8 /* CodeTemplate+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0846B1181EDDF64F00D64D77 /* CodeTemplate+CoreDataProperties.swift */; }; @@ -3011,7 +2977,7 @@ 2C9731F51F4C38F600AC9301 /* UpdatePreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6C1CD102DF00D94613 /* UpdatePreferencesContainer.swift */; }; 2C9731F61F4C38F600AC9301 /* VersionUpdateAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6F1CD111F200D94613 /* VersionUpdateAlertConstructor.swift */; }; 2C9731F71F4C38F600AC9301 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E711CD1173200D94613 /* Version.swift */; }; - 2C9731F81F4C38F600AC9301 /* StreaksNotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */; }; + 2C9731F81F4C38F600AC9301 /* NotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */; }; 2C9731FA1F4C38F600AC9301 /* Executable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6711CD80A9600B45652 /* Executable.swift */; }; 2C9731FB1F4C38F600AC9301 /* ExecutionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6741CD8106E00B45652 /* ExecutionQueue.swift */; }; 2C9731FC1F4C38F600AC9301 /* PersistentQueueRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0869F6D11CE216B600F8A6DB /* PersistentQueueRecoveryManager.swift */; }; @@ -3030,7 +2996,6 @@ 2C97320B1F4C38F600AC9301 /* PersistentTaskManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08BC47081CD9FE10009A1D25 /* PersistentTaskManagerProtocol.swift */; }; 2C97320C1F4C38F600AC9301 /* SearchQueriesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E8F9661F34DD2C008CF4A1 /* SearchQueriesPresenter.swift */; }; 2C97320D1F4C38F600AC9301 /* RoutingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082BE3B11E67686B006BC60F /* RoutingManager.swift */; }; - 2C97320E1F4C38F600AC9301 /* NotificationRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */; }; 2C97320F1F4C38F600AC9301 /* NotificationAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0870392F1CD7413D00B6571B /* NotificationAlertConstructor.swift */; }; 2C9732101F4C38F600AC9301 /* DeviceDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AC214A1CE0DE9B00FBB9CD /* DeviceDefaults.swift */; }; 2C9732131F4C38F600AC9301 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083540661CE5FC0E00BDFEA5 /* Notification.swift */; }; @@ -3118,9 +3083,7 @@ 2C9732701F4C38F600AC9301 /* StepsControllerDeepLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085C4FF51D8C835600B27C95 /* StepsControllerDeepLinkRouter.swift */; }; 2C9732711F4C38F600AC9301 /* UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6BB6A1DC8EB45006622EC /* UserActivity.swift */; }; 2C9732721F4C38F600AC9301 /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749251DE5AE0400144C14 /* Alerts.swift */; }; - 2C9732731F4C38F600AC9301 /* StreaksAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */; }; 2C9732741F4C38F600AC9301 /* QuizPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1FC321F41E74500E14B46 /* QuizPresenter.swift */; }; - 2C9732751F4C38F600AC9301 /* StreaksStepikAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */; }; 2C9732761F4C38F600AC9301 /* InputAccessoryBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9141F10EA690087D61B /* InputAccessoryBuilder.swift */; }; 2C9732771F4C38F600AC9301 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492C1DE5B07900144C14 /* AlertManager.swift */; }; 2C9732781F4C38F600AC9301 /* CodeSuggestionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FEFC1A1F117257005CA0FB /* CodeSuggestionTableViewCell.swift */; }; @@ -3258,6 +3221,8 @@ 2CA2E72720237E5B001DC410 /* CongratulationAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86BB7C04201953AE00063538 /* CongratulationAlertManager.swift */; }; 2CA2E72820237E5E001DC410 /* CongratulationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86BB7C05201953AF00063538 /* CongratulationViewController.swift */; }; 2CA2E72920237E61001DC410 /* CongratulationViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 86BB7C06201953AF00063538 /* CongratulationViewController.xib */; }; + 2CA3DAA12179C6F600F43888 /* NotificationsRegistrationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3DAA02179C6F600F43888 /* NotificationsRegistrationService.swift */; }; + 2CA3DAA32179C71500F43888 /* NotificationPermissionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3DAA22179C71500F43888 /* NotificationPermissionStatus.swift */; }; 2CA3F55D20FF79860041E893 /* GraphServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F55C20FF79860041E893 /* GraphServiceProtocol.swift */; }; 2CA3F56120FF7C030041E893 /* GraphService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA3F56020FF7C030041E893 /* GraphService.swift */; }; 2CA5568B2123204B00A3B1AB /* StepPlainObject+Make.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA5568A2123204B00A3B1AB /* StepPlainObject+Make.swift */; }; @@ -3304,7 +3269,6 @@ 2CA8D15E2088D9C000E105E9 /* RateAppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0890560F1E98021000B8FE6A /* RateAppViewController.swift */; }; 2CA8D15F2088D9C000E105E9 /* StepikLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DF78D11F64059900AEEA85 /* StepikLabel.swift */; }; 2CA8D1602088D9C000E105E9 /* CardStepViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5DF13E1FED26AC003B1177 /* CardStepViewController.swift */; }; - 2CA8D1612088D9C000E105E9 /* CustomSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 081566241F69C3AD0082B359 /* CustomSearchBar.swift */; }; 2CA8D1622088D9C000E105E9 /* LaunchDefaultsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0890560C1E97CE1800B8FE6A /* LaunchDefaultsContainer.swift */; }; 2CA8D1642088D9C000E105E9 /* DefaultsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089056091E97CD7900B8FE6A /* DefaultsContainer.swift */; }; 2CA8D1652088D9C000E105E9 /* CoursesAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080CE1421E955D300089A27F /* CoursesAPI.swift */; }; @@ -3454,7 +3418,6 @@ 2CA8D2132088D9C000E105E9 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D2AE451C04BA6100BD8C3D /* Time.swift */; }; 2CA8D2142088D9C000E105E9 /* AnalyticsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08D2AE491C05127500BD8C3D /* AnalyticsHelper.swift */; }; 2CA8D2152088D9C000E105E9 /* ControllerHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D64AE1C19BDB2003222F0 /* ControllerHelper.swift */; }; - 2CA8D2162088D9C000E105E9 /* NotificationRequestAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */; }; 2CA8D2172088D9C000E105E9 /* GCDThings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F5556B1C5267A600C877E8 /* GCDThings.swift */; }; 2CA8D2182088D9C000E105E9 /* TooltipDefaultsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 087296DB20168FF2009F9256 /* TooltipDefaultsManager.swift */; }; 2CA8D21A2088D9C000E105E9 /* GlobalFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086A9D611C74AF90003611DC /* GlobalFunctions.swift */; }; @@ -3506,10 +3469,9 @@ 2CA8D24D2088D9C000E105E9 /* UpdatePreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6C1CD102DF00D94613 /* UpdatePreferencesContainer.swift */; }; 2CA8D24E2088D9C000E105E9 /* VersionUpdateAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6F1CD111F200D94613 /* VersionUpdateAlertConstructor.swift */; }; 2CA8D24F2088D9C000E105E9 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E711CD1173200D94613 /* Version.swift */; }; - 2CA8D2502088D9C000E105E9 /* StreaksNotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */; }; + 2CA8D2502088D9C000E105E9 /* NotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */; }; 2CA8D2512088D9C000E105E9 /* Executable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6711CD80A9600B45652 /* Executable.swift */; }; 2CA8D2522088D9C000E105E9 /* ExecutionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6741CD8106E00B45652 /* ExecutionQueue.swift */; }; - 2CA8D2532088D9C000E105E9 /* NotificationPermissionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */; }; 2CA8D2542088D9C000E105E9 /* PersistentQueueRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0869F6D11CE216B600F8A6DB /* PersistentQueueRecoveryManager.swift */; }; 2CA8D2552088D9C000E105E9 /* Queues.plist in Sources */ = {isa = PBXBuildFile; fileRef = 0869F6D41CE229C800F8A6DB /* Queues.plist */; }; 2CA8D2562088D9C000E105E9 /* ExecutionQueues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E67A1CD9483500B45652 /* ExecutionQueues.swift */; }; @@ -3530,7 +3492,6 @@ 2CA8D2662088D9C000E105E9 /* PersistentTaskManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08BC47081CD9FE10009A1D25 /* PersistentTaskManagerProtocol.swift */; }; 2CA8D2672088D9C000E105E9 /* SearchQueriesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E8F9661F34DD2C008CF4A1 /* SearchQueriesPresenter.swift */; }; 2CA8D2682088D9C000E105E9 /* RoutingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082BE3B11E67686B006BC60F /* RoutingManager.swift */; }; - 2CA8D2692088D9C000E105E9 /* NotificationRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */; }; 2CA8D26A2088D9C000E105E9 /* SQLQuizViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE3BCA41FBF0121000AD405 /* SQLQuizViewController.swift */; }; 2CA8D26B2088D9C000E105E9 /* NotificationAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0870392F1CD7413D00B6571B /* NotificationAlertConstructor.swift */; }; 2CA8D26C2088D9C000E105E9 /* UICollectionViewFlowLayout+PlusCrashWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C15EB951FC70A0300F56D93 /* UICollectionViewFlowLayout+PlusCrashWorkaround.swift */; }; @@ -3650,9 +3611,7 @@ 2CA8D2E82088D9C000E105E9 /* StepsControllerDeepLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085C4FF51D8C835600B27C95 /* StepsControllerDeepLinkRouter.swift */; }; 2CA8D2E92088D9C000E105E9 /* UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6BB6A1DC8EB45006622EC /* UserActivity.swift */; }; 2CA8D2EA2088D9C000E105E9 /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749251DE5AE0400144C14 /* Alerts.swift */; }; - 2CA8D2EC2088D9C000E105E9 /* StreaksAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */; }; 2CA8D2ED2088D9C000E105E9 /* QuizPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1FC321F41E74500E14B46 /* QuizPresenter.swift */; }; - 2CA8D2EE2088D9C000E105E9 /* StreaksStepikAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */; }; 2CA8D2EF2088D9C000E105E9 /* InputAccessoryBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9141F10EA690087D61B /* InputAccessoryBuilder.swift */; }; 2CA8D2F02088D9C000E105E9 /* TooltipFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086D5B51201283C2000F7715 /* TooltipFactory.swift */; }; 2CA8D2F12088D9C000E105E9 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492C1DE5B07900144C14 /* AlertManager.swift */; }; @@ -3756,7 +3715,6 @@ 2CA8D36E2088D9C000E105E9 /* UnitTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0828FF8B1BC81F41000AFEA7 /* UnitTableViewCell.xib */; }; 2CA8D36F2088D9C000E105E9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941D1B8C58AC00D278AB /* Images.xcassets */; }; 2CA8D3702088D9C000E105E9 /* arrow_left.svg in Resources */ = {isa = PBXBuildFile; fileRef = 2CB62BED2019FDB100B5E336 /* arrow_left.svg */; }; - 2CA8D3722088D9C000E105E9 /* CustomSearchBar.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0815661A1F69C3910082B359 /* CustomSearchBar.xib */; }; 2CA8D3732088D9C000E105E9 /* AchievementNotificationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C3DA0031F3C68B700D74968 /* AchievementNotificationView.xib */; }; 2CA8D3742088D9C000E105E9 /* plyr.css in Resources */ = {isa = PBXBuildFile; fileRef = 2C4BBF12203DC668000A4250 /* plyr.css */; }; 2CA8D3762088D9C000E105E9 /* NotificationRequestAlertViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08C6E31B1DFB2691007F7E39 /* NotificationRequestAlertViewController.xib */; }; @@ -4157,6 +4115,7 @@ 62E989FCFC8AE5E55920EDAC /* ExploreBlockPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E980ADD37451FFE6C89788 /* ExploreBlockPlaceholderView.swift */; }; 62E98A00AD93EC1151D8C158 /* RouterPoppable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98A35FCB795B26C083C91 /* RouterPoppable.swift */; }; 62E98A020CCA8FBA72EAE811 /* ProfileViewController+StreakNotificationsControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9888C0199CE31C6B4E91D /* ProfileViewController+StreakNotificationsControlView.swift */; }; + 62E98A53567525055BCCA089 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E986DB2768602666465180 /* AppDelegate.swift */; }; 62E98A5A35832390917C34CF /* CourseWidgetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9816CE0B5030624365711 /* CourseWidgetViewModel.swift */; }; 62E98A973A47105C195530CD /* AchievementsRetriever.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E986C77C97CA8353D93E25 /* AchievementsRetriever.swift */; }; 62E98AA8F1EFF1FE83F0C13E /* CourseListDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98531F389566315A3D2EE /* CourseListDataFlow.swift */; }; @@ -4332,7 +4291,7 @@ 864D66BA1E83DE03001E8D9E /* UpdatePreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6C1CD102DF00D94613 /* UpdatePreferencesContainer.swift */; }; 864D66BB1E83DE03001E8D9E /* VersionUpdateAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E6F1CD111F200D94613 /* VersionUpdateAlertConstructor.swift */; }; 864D66BC1E83DE03001E8D9E /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08901E711CD1173200D94613 /* Version.swift */; }; - 864D66BD1E83DE03001E8D9E /* StreaksNotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */; }; + 864D66BD1E83DE03001E8D9E /* NotificationSuggestionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */; }; 864D66BF1E83DE03001E8D9E /* Executable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6711CD80A9600B45652 /* Executable.swift */; }; 864D66C01E83DE03001E8D9E /* ExecutionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0861E6741CD8106E00B45652 /* ExecutionQueue.swift */; }; 864D66C11E83DE03001E8D9E /* PersistentQueueRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0869F6D11CE216B600F8A6DB /* PersistentQueueRecoveryManager.swift */; }; @@ -4347,7 +4306,6 @@ 864D66CA1E83DE03001E8D9E /* ExecutableTaskTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08BC47131CDA3E99009A1D25 /* ExecutableTaskTypes.swift */; }; 864D66CB1E83DE03001E8D9E /* DictionarySerializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083267A81CDCF59B002F7B5A /* DictionarySerializable.swift */; }; 864D66CC1E83DE03001E8D9E /* PersistentTaskManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08BC47081CD9FE10009A1D25 /* PersistentTaskManagerProtocol.swift */; }; - 864D66CD1E83DE03001E8D9E /* NotificationRegistrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */; }; 864D66CE1E83DE03001E8D9E /* NotificationAlertConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0870392F1CD7413D00B6571B /* NotificationAlertConstructor.swift */; }; 864D66CF1E83DE03001E8D9E /* DeviceDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08AC214A1CE0DE9B00FBB9CD /* DeviceDefaults.swift */; }; 864D66D21E83DE03001E8D9E /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083540661CE5FC0E00BDFEA5 /* Notification.swift */; }; @@ -4422,8 +4380,6 @@ 864D671D1E83DE03001E8D9E /* StepsControllerDeepLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 085C4FF51D8C835600B27C95 /* StepsControllerDeepLinkRouter.swift */; }; 864D671E1E83DE03001E8D9E /* UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08E6BB6A1DC8EB45006622EC /* UserActivity.swift */; }; 864D671F1E83DE03001E8D9E /* Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749251DE5AE0400144C14 /* Alerts.swift */; }; - 864D67201E83DE03001E8D9E /* StreaksAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */; }; - 864D67211E83DE03001E8D9E /* StreaksStepikAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */; }; 864D67221E83DE03001E8D9E /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492C1DE5B07900144C14 /* AlertManager.swift */; }; 864D67231E83DE03001E8D9E /* PreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0837492F1DE5BBF700144C14 /* PreferencesContainer.swift */; }; 864D67241E83DE03001E8D9E /* NotificationPreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083749331DE5BC2A00144C14 /* NotificationPreferencesContainer.swift */; }; @@ -4964,7 +4920,7 @@ 0800B81E1D06FDE4006C987E /* DiscussionProxiesAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscussionProxiesAPI.swift; sourceTree = ""; }; 0800B8211D07029B006C987E /* CommentsAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentsAPI.swift; sourceTree = ""; }; 0800B8241D0704B6006C987E /* ApiUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiUtil.swift; sourceTree = ""; }; - 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreaksNotificationSuggestionManager.swift; sourceTree = ""; }; + 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSuggestionManager.swift; sourceTree = ""; }; 080217791F54E91300186245 /* MenuBlocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuBlocks.swift; sourceTree = ""; }; 080217821F55B1B200186245 /* Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = ""; }; 0802AC531C7222B200C4F3E6 /* Model_v2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_v2.xcdatamodel; sourceTree = ""; }; @@ -5019,8 +4975,6 @@ 081387E01D7AF7700092E05D /* StyledTabBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledTabBarViewController.swift; sourceTree = ""; }; 0813EEA41BFE5A5400DB4B83 /* Assignment+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Assignment+CoreDataProperties.swift"; sourceTree = ""; }; 0813EEA51BFE5A5400DB4B83 /* Assignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Assignment.swift; sourceTree = ""; }; - 0815661A1F69C3910082B359 /* CustomSearchBar.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CustomSearchBar.xib; sourceTree = ""; }; - 081566241F69C3AD0082B359 /* CustomSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomSearchBar.swift; sourceTree = ""; }; 08194EB920B813AC00B34327 /* PersonalDeadlineCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalDeadlineCounter.swift; sourceTree = ""; }; 08194EBB20B89B0900B34327 /* Model_timetocomplete_v22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_timetocomplete_v22.xcdatamodel; sourceTree = ""; }; 0819856820BF242E00897BBA /* PersonalDeadlineEditScheduleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalDeadlineEditScheduleViewController.swift; sourceTree = ""; }; @@ -5064,7 +5018,6 @@ 0829ED9320C865BB0018E6FF /* ContentLanguagePreferenceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLanguagePreferenceTableViewCell.swift; sourceTree = ""; }; 0829ED9420C865BB0018E6FF /* ContentLanguagePreferenceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ContentLanguagePreferenceTableViewCell.xib; sourceTree = ""; }; 0829ED9820C8825E0018E6FF /* ExploreDefaultsContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreDefaultsContainer.swift; sourceTree = ""; }; - 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionManager.swift; sourceTree = ""; }; 082B54CE20A9DD3000144817 /* PersonalDeadlinesModeSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalDeadlinesModeSelectionViewController.swift; sourceTree = ""; }; 082B54D020A9DD3C00144817 /* PersonalDeadlines.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PersonalDeadlines.storyboard; sourceTree = ""; }; 082B54D420AA290800144817 /* PersonalDeadlinesDefaultsContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalDeadlinesDefaultsContainer.swift; sourceTree = ""; }; @@ -5088,12 +5041,10 @@ 0834C46E1E2CEC4E002F8516 /* MatchingQuizViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatchingQuizViewController.swift; sourceTree = ""; }; 083540601CE5DD5000BDFEA5 /* NotificationReactionHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NotificationReactionHandler.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 083540661CE5FC0E00BDFEA5 /* Notification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; - 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = NotificationRegistrator.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 083622D71CD1FA4800CD8915 /* StepTabView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepTabView.swift; sourceTree = ""; }; 083622DB1CD1FA6700CD8915 /* StepTabView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StepTabView.xib; sourceTree = ""; }; 083622E11CD24C2100CD8915 /* Model_v4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_v4.xcdatamodel; sourceTree = ""; }; 083749251DE5AE0400144C14 /* Alerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Alerts.swift; sourceTree = ""; }; - 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreaksAlertManager.swift; sourceTree = ""; }; 0837492C1DE5B07900144C14 /* AlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = ""; }; 0837492F1DE5BBF700144C14 /* PreferencesContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesContainer.swift; sourceTree = ""; }; 083749331DE5BC2A00144C14 /* NotificationPreferencesContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationPreferencesContainer.swift; sourceTree = ""; }; @@ -5129,7 +5080,6 @@ 084156911BCBFFBD006B8C73 /* Step+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Step+CoreDataProperties.swift"; sourceTree = ""; }; 084156921BCBFFBD006B8C73 /* Step.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Step.swift; sourceTree = ""; }; 0841BDC61E082AE7008CE13E /* WatchDataHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchDataHelper.swift; sourceTree = ""; }; - 08421BC821764C9600E8A81B /* AuthAfterOnboardingSplitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthAfterOnboardingSplitTest.swift; sourceTree = ""; }; 08421BCA21764FC400E8A81B /* ActiveSplitTestsContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSplitTestsContainer.swift; sourceTree = ""; }; 084472041D05918E00197166 /* ChoiceQuizTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChoiceQuizTableViewCell.swift; sourceTree = ""; }; 084472051D05918E00197166 /* ChoiceQuizTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChoiceQuizTableViewCell.xib; sourceTree = ""; }; @@ -5158,6 +5108,7 @@ 08484EFD211AF4320006266F /* TextStoryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextStoryView.swift; sourceTree = ""; }; 08484F19211B5B750006266F /* Skeletonable+UICollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Skeletonable+UICollectionView.swift"; sourceTree = ""; }; 08484F1B211B5DB20006266F /* StorySkeletonPlaceholderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StorySkeletonPlaceholderView.xib; sourceTree = ""; }; + 0848BE8A218379A400762DC1 /* JoinCourseStringSplitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinCourseStringSplitTest.swift; sourceTree = ""; }; 084A2E5B1D80A83800C6702B /* Model_v8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model_v8.xcdatamodel; sourceTree = ""; }; 084BD9BA1E5368B600B1901E /* PickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickerViewController.swift; sourceTree = ""; }; 084C658C1FDAD04C006A3E17 /* RemoteConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteConfig.swift; sourceTree = ""; }; @@ -5355,7 +5306,6 @@ 08CBA3261F57563C00302154 /* TransitionMenuBlockTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TransitionMenuBlockTableViewCell.xib; sourceTree = ""; }; 08CBA34A1F57734900302154 /* ProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; 08CBA3531F5A2D9400302154 /* Profile.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Profile.storyboard; sourceTree = ""; }; - 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRequestAlertManager.swift; sourceTree = ""; }; 08CE16DF1BFA5A80008511B7 /* DeviceInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceInfo.swift; sourceTree = ""; }; 08D035201D65A252003515C6 /* AnalyticsEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyticsEvents.swift; sourceTree = ""; }; 08D035241D65B5E5003515C6 /* AnalyticsReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyticsReporter.swift; sourceTree = ""; }; @@ -5382,7 +5332,6 @@ 08DDF90F20A9B2E9004ECC11 /* PersonalDeadlinesSuggestionWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalDeadlinesSuggestionWidgetView.swift; sourceTree = ""; }; 08DE94111B8C58AC00D278AB /* Stepic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Stepic.app; sourceTree = BUILT_PRODUCTS_DIR; }; 08DE94151B8C58AC00D278AB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 08DE94161B8C58AC00D278AB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 08DE941B1B8C58AC00D278AB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 08DE941D1B8C58AC00D278AB /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 08DE94201B8C58AC00D278AB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; @@ -5475,7 +5424,6 @@ 08FEFC1A1F117257005CA0FB /* CodeSuggestionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeSuggestionTableViewCell.swift; sourceTree = ""; }; 08FEFC1B1F117257005CA0FB /* CodeSuggestionTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CodeSuggestionTableViewCell.xib; sourceTree = ""; }; 08FEFC201F127470005CA0FB /* AutocompleteWords.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteWords.swift; sourceTree = ""; }; - 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreaksStepikAlertManager.swift; sourceTree = ""; }; 0C05D9DC092E240ED7A0BD28 /* Pods_Adaptive_1838_Screenshots.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Adaptive_1838_Screenshots.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0DB2BD32605F5F5C7BC0A455 /* Pods_Adaptive_3150.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Adaptive_3150.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 14692EFFC3DC1466AA55B1DD /* BaseExplorePresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BaseExplorePresenter.swift; sourceTree = ""; }; @@ -5544,6 +5492,7 @@ 2C0159E0213940850043DBFF /* LearningRouterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearningRouterProtocol.swift; sourceTree = ""; }; 2C0159E2213940910043DBFF /* LearningRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearningRouter.swift; sourceTree = ""; }; 2C0159E521394C9E0043DBFF /* TrainingCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrainingCollectionViewController.swift; sourceTree = ""; }; + 2C0176C02188953700DDB9D0 /* NotificationAlertsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationAlertsAnalytics.swift; sourceTree = ""; }; 2C03B2A31F0CD87600005383 /* StringHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringHelper.swift; sourceTree = ""; }; 2C0AF18B203C67EC000EA3B6 /* MigrationExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrationExtensions.swift; sourceTree = ""; }; 2C104B672069064D0026FEB9 /* autocomplete_suggestions.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = autocomplete_suggestions.plist; sourceTree = ""; }; @@ -5620,6 +5569,10 @@ 2C2485482101EE3E006F8858 /* DownloaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloaderTests.swift; sourceTree = ""; }; 2C2544081F3480BE004DB3D9 /* AchievementNotificationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementNotificationView.swift; sourceTree = ""; }; 2C2EA36A212D5FEF002116C9 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + 2C2F0BE62186EEB8007DCA0A /* StreakNotificationsRequestAlertDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreakNotificationsRequestAlertDataSource.swift; sourceTree = ""; }; + 2C2F0BE82186EF87007DCA0A /* CommonNotificationsRequestAlertDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonNotificationsRequestAlertDataSource.swift; sourceTree = ""; }; + 2C2F0BEB2186F0C3007DCA0A /* NotificationsRequestAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsRequestAlertPresenter.swift; sourceTree = ""; }; + 2C2F0BED2186F196007DCA0A /* NotificationsRequestAlertDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsRequestAlertDataSource.swift; sourceTree = ""; }; 2C2FD7782107359E00609621 /* LessonsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonsTableViewController.swift; sourceTree = ""; }; 2C2FD77D210735E000609621 /* LessonTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonTableViewCell.swift; sourceTree = ""; }; 2C2FD77E210735E000609621 /* LessonTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LessonTableViewCell.xib; sourceTree = ""; }; @@ -5744,6 +5697,7 @@ 2C770305210F29B0002E250D /* LessonPlainObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LessonPlainObject.swift; sourceTree = ""; }; 2C770308210F2AD5002E250D /* RouterDismissable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouterDismissable.swift; sourceTree = ""; }; 2C770309210F2AD5002E250D /* BaseRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseRouter.swift; sourceTree = ""; }; + 2C79F61721873CD9004CC082 /* NotificationsRequestOnlySettingsAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsRequestOnlySettingsAlertPresenter.swift; sourceTree = ""; }; 2C7CCE272138276200A3AEA8 /* LearningTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearningTableViewController.swift; sourceTree = ""; }; 2C7CCE29213827B400A3AEA8 /* LearningTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearningTableViewCell.swift; sourceTree = ""; }; 2C7CCE2A213827B400A3AEA8 /* LearningTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LearningTableViewCell.xib; sourceTree = ""; }; @@ -5755,6 +5709,7 @@ 2C7FEE791FDAFB4600B2B4F1 /* OnboardingPageView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OnboardingPageView.xib; sourceTree = ""; }; 2C7FEE7C1FDFCE5300B2B4F1 /* OnboardingPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingPageView.swift; sourceTree = ""; }; 2C7FEE7E1FDFCEF200B2B4F1 /* OnboardingAnimatedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingAnimatedView.swift; sourceTree = ""; }; + 2C82D51621830DD500C10805 /* NotificationsRegistrationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsRegistrationServiceProtocol.swift; sourceTree = ""; }; 2C8305E920F38880003D7F9B /* Vertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vertex.swift; sourceTree = ""; }; 2C8305EB20F388B9003D7F9B /* Edge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Edge.swift; sourceTree = ""; }; 2C8305F620F390F8003D7F9B /* GraphTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphTests.swift; sourceTree = ""; }; @@ -5821,6 +5776,8 @@ 2CA2E70620233E28001DC410 /* AdaptiveCardsStepsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCardsStepsPresenter.swift; sourceTree = ""; }; 2CA2E7092023591F001DC410 /* AdaptiveUserActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveUserActions.swift; sourceTree = ""; }; 2CA2E70D20237DB7001DC410 /* AdaptiveCardsStepsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveCardsStepsViewController.swift; sourceTree = ""; }; + 2CA3DAA02179C6F600F43888 /* NotificationsRegistrationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsRegistrationService.swift; sourceTree = ""; }; + 2CA3DAA22179C71500F43888 /* NotificationPermissionStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPermissionStatus.swift; sourceTree = ""; }; 2CA3F55C20FF79860041E893 /* GraphServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphServiceProtocol.swift; sourceTree = ""; }; 2CA3F56020FF7C030041E893 /* GraphService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphService.swift; sourceTree = ""; }; 2CA5568A2123204B00A3B1AB /* StepPlainObject+Make.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StepPlainObject+Make.swift"; sourceTree = ""; }; @@ -6068,6 +6025,7 @@ 62E986B6284822FB1A8E1FCC /* AuthGreetingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthGreetingViewController.swift; sourceTree = ""; }; 62E986C77C97CA8353D93E25 /* AchievementsRetriever.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementsRetriever.swift; sourceTree = ""; }; 62E986D38DE1E4890415F9F7 /* StreakActivityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreakActivityView.swift; sourceTree = ""; }; + 62E986DB2768602666465180 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 62E986DE702390E83FF2AB52 /* CourseListViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseListViewDelegate.swift; sourceTree = ""; }; 62E98708CFECD3F1D0AD4120 /* Achievement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Achievement.swift; sourceTree = ""; }; 62E98719354649AAFC238BDA /* StepsServiceImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepsServiceImpl.swift; sourceTree = ""; }; @@ -6483,16 +6441,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0800B8161D06D933006C987E /* Discussions */ = { - isa = PBXGroup; - children = ( - 0800B8171D06D961006C987E /* DiscussionProxy.swift */, - 0800B81A1D06DC1B006C987E /* Comment.swift */, - 086A8B241D21434B00F45C45 /* Vote.swift */, - ); - name = Discussions; - sourceTree = ""; - }; 0805FE3E1F0D38C5001226B4 /* Code Analysis */ = { isa = PBXGroup; children = ( @@ -6659,15 +6607,6 @@ name = Tests; sourceTree = ""; }; - 081566231F69C39A0082B359 /* Search Bar */ = { - isa = PBXGroup; - children = ( - 0815661A1F69C3910082B359 /* CustomSearchBar.xib */, - 081566241F69C3AD0082B359 /* CustomSearchBar.swift */, - ); - name = "Search Bar"; - sourceTree = ""; - }; 08195A041FA00FE300E6D6CD /* Course List */ = { isa = PBXGroup; children = ( @@ -6828,10 +6767,9 @@ 083554261CC8FD0B004C4E85 /* Notifications */ = { isa = PBXGroup; children = ( - 083554271CC8FD2D004C4E85 /* NotificationRegistrator.swift */, - 0870392F1CD7413D00B6571B /* NotificationAlertConstructor.swift */, 08AC214A1CE0DE9B00FBB9CD /* DeviceDefaults.swift */, - 082A88FD2046F0460079F038 /* NotificationPermissionManager.swift */, + 0870392F1CD7413D00B6571B /* NotificationAlertConstructor.swift */, + 2CA3DAA22179C71500F43888 /* NotificationPermissionStatus.swift */, ); name = Notifications; sourceTree = ""; @@ -6856,9 +6794,9 @@ 083749281DE5AE1400144C14 /* Alerts */ = { isa = PBXGroup; children = ( - 083749251DE5AE0400144C14 /* Alerts.swift */, - 08FF752E1DF9B87A00D656C4 /* Streaks */, + 2C2F0BEA2186EFA0007DCA0A /* NotificationsRequest */, 0837492C1DE5B07900144C14 /* AlertManager.swift */, + 083749251DE5AE0400144C14 /* Alerts.swift */, 082799471E9B81AF008A3786 /* RateAppAlertManager.swift */, ); name = Alerts; @@ -7184,17 +7122,6 @@ name = WebViewHelpers; sourceTree = ""; }; - 0858B7071CFF26F300459A6A /* Utils */ = { - isa = PBXGroup; - children = ( - 0800B8161D06D933006C987E /* Discussions */, - 087F6B951CE9E2C8002649AB /* HTMLParsingUtil.swift */, - 0858B7081CFF271200459A6A /* TagDetectionUtil.swift */, - 089574551E5B76E700C12D21 /* UIImageView+SVGDownload.swift */, - ); - name = Utils; - sourceTree = ""; - }; 0859AE931E4F194D00A0D206 /* FillBlanksQuizViewController */ = { isa = PBXGroup; children = ( @@ -7315,14 +7242,14 @@ path = StickerPackExtension; sourceTree = ""; }; - 0867D82A1E85313100B13285 /* Streaks */ = { + 0867D82A1E85313100B13285 /* NotificationRequest */ = { isa = PBXGroup; children = ( + 2C47A163206284B1003E87EC /* NotificationRequestAlertContext.swift */, 084DDDE0204EDF7500913503 /* NotificationRequestAlertViewController.swift */, 08C6E31B1DFB2691007F7E39 /* NotificationRequestAlertViewController.xib */, - 2C47A163206284B1003E87EC /* NotificationRequestAlertContext.swift */, ); - name = Streaks; + name = NotificationRequest; sourceTree = ""; }; 08687BAB1BACB41F00F5BDE1 /* Generic */ = { @@ -7576,8 +7503,8 @@ 089877B4214061EC0065DFA2 /* Active Tests */ = { isa = PBXGroup; children = ( - 08421BC821764C9600E8A81B /* AuthAfterOnboardingSplitTest.swift */, 08421BCA21764FC400E8A81B /* ActiveSplitTestsContainer.swift */, + 0848BE8A218379A400762DC1 /* JoinCourseStringSplitTest.swift */, ); name = "Active Tests"; sourceTree = ""; @@ -7768,19 +7695,6 @@ name = Menu; sourceTree = ""; }; - 08D035231D65B5C5003515C6 /* Analytics */ = { - isa = PBXGroup; - children = ( - 089877A4214047BB0065DFA2 /* AnalyticsUserPropertiesServiceProtocol.swift */, - 087235ED20FE2F2F00B4D5B1 /* AnalyticsEvent.swift */, - 08D035201D65A252003515C6 /* AnalyticsEvents.swift */, - 08D035241D65B5E5003515C6 /* AnalyticsReporter.swift */, - 081A14FE20D963090016364E /* AmplitudeAnalyticsEvents.swift */, - 08E7CA0F20DAF6E0004F8563 /* AnalyticsUserProperties.swift */, - ); - name = Analytics; - sourceTree = ""; - }; 08D2F72A2122D9DE009BA052 /* Transitions */ = { isa = PBXGroup; children = ( @@ -7872,6 +7786,7 @@ 08DE94131B8C58AC00D278AB /* Stepic */ = { isa = PBXGroup; children = ( + 62E986DB2768602666465180 /* AppDelegate.swift */, 08E43E95214C277600E3CB50 /* Transition Routers */, 2C89089E216F460F00083341 /* Services */, 0853A3DE213D8F6100931F72 /* Split tests */, @@ -7883,13 +7798,12 @@ 08DB7DAA1D50F92B0006E9F6 /* Stepic.entitlements */, 086E965C1BF6682000AB952D /* Views */, 0885F84A1BA8375C00F2A188 /* Controllers */, - 08D035231D65B5C5003515C6 /* Analytics */, + 2C0176C32188A49100DDB9D0 /* Analytics */, 0808A30A1BC84FBB00A95C29 /* Helpers */, - 0858B7071CFF26F300459A6A /* Utils */, + 2CA3DAA52179DF5900F43888 /* Utils */, 0885F8491BA8374000F2A188 /* Model */, 08F204931BB0157F00D080C0 /* Extensions */, 08E542041C6C760C00DEC38E /* Enums */, - 08DE94161B8C58AC00D278AB /* AppDelegate.swift */, 08DE941A1B8C58AC00D278AB /* Main.storyboard */, 08DE941D1B8C58AC00D278AB /* Images.xcassets */, 08DE941F1B8C58AC00D278AB /* LaunchScreen.xib */, @@ -7969,7 +7883,6 @@ isa = PBXGroup; children = ( 2CFDB1231F559F9A00B8035C /* AvatarImageView.swift */, - 081566231F69C39A0082B359 /* Search Bar */, 08DF78D11F64059900AEEA85 /* StepikLabel.swift */, 08EDD6291F7C6785005203E4 /* StepikButton.swift */, 086D5B4720128398000F7715 /* Tooltip */, @@ -8032,15 +7945,15 @@ 08E542071C6CD59A00DEC38E /* Managers */ = { isa = PBXGroup; children = ( - 0829B8311E9C4CB5009B4A84 /* RateAppManager.swift */, + 08901E661CD0F94C00D94613 /* In-app version control */, 082BE3B01E676856006BC60F /* RoutingManagers */, - 08C5E4FC1C315272004AA626 /* AudioManager.swift */, - 08C9F7CF1C29AE8B00E544D0 /* WebControllerManager.swift */, 08CB4ABB1C4D3DAB00D7F918 /* WKWebViewPanelManager */, + 08C5E4FC1C315272004AA626 /* AudioManager.swift */, 08E542081C6CD65500DEC38E /* CourseSubscriptionManager.swift */, - 08901E661CD0F94C00D94613 /* In-app version control */, - 08019A811DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift */, + 08019A811DEED7E900691F0B /* NotificationSuggestionManager.swift */, + 0829B8311E9C4CB5009B4A84 /* RateAppManager.swift */, 08B062DC1FDEFD5900A6C999 /* StreaksAlertPresentationManager.swift */, + 08C9F7CF1C29AE8B00E544D0 /* WebControllerManager.swift */, ); name = Managers; sourceTree = ""; @@ -8260,21 +8173,11 @@ children = ( 0890560F1E98021000B8FE6A /* RateAppViewController.swift */, 089056101E98021000B8FE6A /* RateAppViewController.xib */, - 0867D82A1E85313100B13285 /* Streaks */, - 08CCAEC0204F0B2D002B4544 /* NotificationRequestAlertManager.swift */, + 0867D82A1E85313100B13285 /* NotificationRequest */, ); name = Alerts; sourceTree = ""; }; - 08FF752E1DF9B87A00D656C4 /* Streaks */ = { - isa = PBXGroup; - children = ( - 083749291DE5AF8A00144C14 /* StreaksAlertManager.swift */, - 08FF752F1DF9B89000D656C4 /* StreaksStepikAlertManager.swift */, - ); - name = Streaks; - sourceTree = ""; - }; 0A2B4BA9DF619D45E14C11A4 /* View */ = { isa = PBXGroup; children = ( @@ -8607,6 +8510,20 @@ path = Router; sourceTree = ""; }; + 2C0176C32188A49100DDB9D0 /* Analytics */ = { + isa = PBXGroup; + children = ( + 081A14FE20D963090016364E /* AmplitudeAnalyticsEvents.swift */, + 087235ED20FE2F2F00B4D5B1 /* AnalyticsEvent.swift */, + 08D035201D65A252003515C6 /* AnalyticsEvents.swift */, + 08D035241D65B5E5003515C6 /* AnalyticsReporter.swift */, + 08E7CA0F20DAF6E0004F8563 /* AnalyticsUserProperties.swift */, + 089877A4214047BB0065DFA2 /* AnalyticsUserPropertiesServiceProtocol.swift */, + 2C0176C02188953700DDB9D0 /* NotificationAlertsAnalytics.swift */, + ); + path = Analytics; + sourceTree = ""; + }; 2C03B2A21F0CD84900005383 /* Helpers */ = { isa = PBXGroup; children = ( @@ -8638,6 +8555,15 @@ path = AuthSignInViewController; sourceTree = ""; }; + 2C10AFE8217E04780019966D /* Registration */ = { + isa = PBXGroup; + children = ( + 2C82D51621830DD500C10805 /* NotificationsRegistrationServiceProtocol.swift */, + 2CA3DAA02179C6F600F43888 /* NotificationsRegistrationService.swift */, + ); + path = Registration; + sourceTree = ""; + }; 2C130E872125A3E40022E998 /* Assemblies */ = { isa = PBXGroup; children = ( @@ -8942,6 +8868,26 @@ name = AchievementNotificationView; sourceTree = ""; }; + 2C2F0BEA2186EFA0007DCA0A /* NotificationsRequest */ = { + isa = PBXGroup; + children = ( + 2C2F0BEF2186F1B6007DCA0A /* DataSource */, + 2C2F0BEB2186F0C3007DCA0A /* NotificationsRequestAlertPresenter.swift */, + 2C79F61721873CD9004CC082 /* NotificationsRequestOnlySettingsAlertPresenter.swift */, + ); + name = NotificationsRequest; + sourceTree = ""; + }; + 2C2F0BEF2186F1B6007DCA0A /* DataSource */ = { + isa = PBXGroup; + children = ( + 2C2F0BED2186F196007DCA0A /* NotificationsRequestAlertDataSource.swift */, + 2C2F0BE82186EF87007DCA0A /* CommonNotificationsRequestAlertDataSource.swift */, + 2C2F0BE62186EEB8007DCA0A /* StreakNotificationsRequestAlertDataSource.swift */, + ); + name = DataSource; + sourceTree = ""; + }; 2C2FD7762107355D00609621 /* Lessons */ = { isa = PBXGroup; children = ( @@ -9797,6 +9743,37 @@ path = Router; sourceTree = ""; }; + 2CA3DA9F2179C38400F43888 /* LocalNotifications */ = { + isa = PBXGroup; + children = ( + 2CAD8B9A217096AB003F420B /* LocalNotificationsService.swift */, + 2CAD8B9C2170CDB4003F420B /* LocalNotificationContentProvider.swift */, + 2C55877F217521E8009E1BDE /* LocalNotificationsMigrator.swift */, + ); + path = LocalNotifications; + sourceTree = ""; + }; + 2CA3DAA52179DF5900F43888 /* Utils */ = { + isa = PBXGroup; + children = ( + 2CA3DAA62179DF7300F43888 /* Discussions */, + 087F6B951CE9E2C8002649AB /* HTMLParsingUtil.swift */, + 0858B7081CFF271200459A6A /* TagDetectionUtil.swift */, + 089574551E5B76E700C12D21 /* UIImageView+SVGDownload.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 2CA3DAA62179DF7300F43888 /* Discussions */ = { + isa = PBXGroup; + children = ( + 0800B81A1D06DC1B006C987E /* Comment.swift */, + 0800B8171D06D961006C987E /* DiscussionProxy.swift */, + 086A8B241D21434B00F45C45 /* Vote.swift */, + ); + path = Discussions; + sourceTree = ""; + }; 2CA5E5A4200E1EC200CE77B0 /* Rating & Stats */ = { isa = PBXGroup; children = ( @@ -9915,10 +9892,9 @@ 2CAD8B9E2170CDF7003F420B /* Notifications */ = { isa = PBXGroup; children = ( + 2CA3DA9F2179C38400F43888 /* LocalNotifications */, + 2C10AFE8217E04780019966D /* Registration */, 2C89089F216F465200083341 /* NotificationsService.swift */, - 2CAD8B9A217096AB003F420B /* LocalNotificationsService.swift */, - 2CAD8B9C2170CDB4003F420B /* LocalNotificationContentProvider.swift */, - 2C55877F217521E8009E1BDE /* LocalNotificationsMigrator.swift */, 2CE02A762176649F009C633C /* UserNotificationsCenterDelegate.swift */, ); path = Notifications; @@ -11931,7 +11907,6 @@ 08047E6F1BFDC0480071C875 /* TitleTextTableViewCell.xib in Resources */, 081A7DF51C56A0DD00583728 /* UnknownTypeQuizViewController.xib in Resources */, 08E542011C6A76E100DEC38E /* MathJax in Resources */, - 0815661B1F69C3910082B359 /* CustomSearchBar.xib in Resources */, 08D2AE481C04F52300BD8C3D /* GoogleService-Info.plist in Resources */, 2CB2394120BB4EFD00F0FE3B /* ProfileDescriptionContentView.xib in Resources */, 0859AEAA1E4F26C700A0D206 /* FillBlanksTextTableViewCell.xib in Resources */, @@ -12129,7 +12104,6 @@ 86A1C32A2065157F00D0914C /* StepikPlaceholderView.xib in Resources */, 08CBA3231F57562A00302154 /* SwitchMenuBlockTableViewCell.xib in Resources */, 2C608AC6204568C5006870BB /* CongratulationViewController.xib in Resources */, - 081566211F69C3910082B359 /* CustomSearchBar.xib in Resources */, 2C1B62861F4C4AEF00236804 /* UnknownTypeQuizViewController.xib in Resources */, 2C1B62871F4C4AEF00236804 /* CodeInputAccessoryCollectionViewCell.xib in Resources */, 2C608ACC204568D5006870BB /* LeaderboardTableViewCell.xib in Resources */, @@ -12220,7 +12194,6 @@ 86A1C3292065157E00D0914C /* StepikPlaceholderView.xib in Resources */, 08CBA3241F57562A00302154 /* SwitchMenuBlockTableViewCell.xib in Resources */, 2C608AC5204568C5006870BB /* CongratulationViewController.xib in Resources */, - 081566221F69C3910082B359 /* CustomSearchBar.xib in Resources */, 2C1B64981F4C590700236804 /* SearchSuggestionTableViewCell.xib in Resources */, 2C1B64E81F4C5A9E00236804 /* Icons.xcassets in Resources */, 2C608ACD204568D5006870BB /* LeaderboardTableViewCell.xib in Resources */, @@ -12378,7 +12351,6 @@ 2C89AB271F4C289900227C3B /* UnitTableViewCell.xib in Resources */, 2C89AB281F4C289900227C3B /* Images.xcassets in Resources */, 2C6A76442045CECC00C509A6 /* arrow_left.svg in Resources */, - 0815661F1F69C3910082B359 /* CustomSearchBar.xib in Resources */, 2C89AB2C1F4C289900227C3B /* AchievementNotificationView.xib in Resources */, 2C4BBF1C203DC66F000A4250 /* plyr.css in Resources */, 087E8B25204DE2A000C35B66 /* NotificationRequestAlertViewController.xib in Resources */, @@ -12447,7 +12419,6 @@ 2C9732C61F4C38F600AC9301 /* step4.html in Resources */, 08CBA3221F57562A00302154 /* SwitchMenuBlockTableViewCell.xib in Resources */, 2C608AC7204568C6006870BB /* CongratulationViewController.xib in Resources */, - 081566201F69C3910082B359 /* CustomSearchBar.xib in Resources */, 2C9732C81F4C38F600AC9301 /* SearchSuggestionTableViewCell.xib in Resources */, 2C97330C1F4C394A00AC9301 /* Icons.xcassets in Resources */, 2C608ACB204568D5006870BB /* LeaderboardTableViewCell.xib in Resources */, @@ -12561,7 +12532,6 @@ 2CA8D36E2088D9C000E105E9 /* UnitTableViewCell.xib in Resources */, 2CA8D36F2088D9C000E105E9 /* Images.xcassets in Resources */, 2CA8D3702088D9C000E105E9 /* arrow_left.svg in Resources */, - 2CA8D3722088D9C000E105E9 /* CustomSearchBar.xib in Resources */, 2CA8D3732088D9C000E105E9 /* AchievementNotificationView.xib in Resources */, 2CA8D3742088D9C000E105E9 /* plyr.css in Resources */, 2CA8D3762088D9C000E105E9 /* NotificationRequestAlertViewController.xib in Resources */, @@ -12673,7 +12643,6 @@ 08CBA3561F5A2D9400302154 /* Profile.storyboard in Resources */, 08E8F97F1F34E64E008CF4A1 /* SearchSuggestionTableViewCell.xib in Resources */, 08CBA31F1F57562A00302154 /* SwitchMenuBlockTableViewCell.xib in Resources */, - 0815661D1F69C3910082B359 /* CustomSearchBar.xib in Resources */, 86AE947F1E84511A00F67691 /* UnknownTypeQuizViewController.xib in Resources */, 0888D1071F1E41FF00A16863 /* CodeInputAccessoryCollectionViewCell.xib in Resources */, 08CBA3311F57563C00302154 /* TransitionMenuBlockTableViewCell.xib in Resources */, @@ -14388,6 +14357,7 @@ 0895A13B1E43836B00FE22DD /* FillBlanksReply.swift in Sources */, 08F485AC1C580DB3000165AA /* MathQuizViewController.swift in Sources */, 08B062DD1FDEFD5900A6C999 /* StreaksAlertPresentationManager.swift in Sources */, + 2CA3DAA12179C6F600F43888 /* NotificationsRegistrationService.swift in Sources */, 083749301DE5BBF700144C14 /* PreferencesContainer.swift in Sources */, 08CBA34B1F57734900302154 /* ProfileViewController.swift in Sources */, 9F9C71521E076B3E00EC8DA3 /* WatchSessionDataObserver.swift in Sources */, @@ -14408,6 +14378,7 @@ 082E35AE20B55691006E28F9 /* StorageRecord.swift in Sources */, 0859AEA81E4F26C700A0D206 /* FillBlanksTextTableViewCell.swift in Sources */, 2CFDB1241F559F9A00B8035C /* AvatarImageView.swift in Sources */, + 2C2F0BEC2186F0C3007DCA0A /* NotificationsRequestAlertPresenter.swift in Sources */, 0846B1151EDDF63200D64D77 /* CodeTemplate.swift in Sources */, 087235EE20FE2F2F00B4D5B1 /* AnalyticsEvent.swift in Sources */, 08484F18211AF4320006266F /* TextStoryView.swift in Sources */, @@ -14436,6 +14407,7 @@ 08CBA2F81F573F6900302154 /* MenuViewController.swift in Sources */, 2CB9E8C21F7AA58B0004E17F /* NotificationsViewController.swift in Sources */, 2C23C5E01F6AB2AD00FC2B7C /* SocialAuthProviders.swift in Sources */, + 2C79F61821873CD9004CC082 /* NotificationsRequestOnlySettingsAlertPresenter.swift in Sources */, 081B7E2A1BAC208200554153 /* StandardsExtensions.swift in Sources */, 0860D9151F10EA690087D61B /* InputAccessoryBuilder.swift in Sources */, 08D2AE4A1C05127500BD8C3D /* AnalyticsHelper.swift in Sources */, @@ -14454,7 +14426,7 @@ 0861E67B1CD9483500B45652 /* ExecutionQueues.swift in Sources */, 0834C46C1E2CE66B002F8516 /* MatchingReply.swift in Sources */, 088E58C11DE34E2F0009B9CE /* SocialSDKProvider.swift in Sources */, - 08019A821DEED7E900691F0B /* StreaksNotificationSuggestionManager.swift in Sources */, + 08019A821DEED7E900691F0B /* NotificationSuggestionManager.swift in Sources */, 08CE16E01BFA5A80008511B7 /* DeviceInfo.swift in Sources */, 2CE527C12029D8EE0047EC5F /* AdaptiveStatsSection.swift in Sources */, 08EDD62A1F7C6785005203E4 /* StepikButton.swift in Sources */, @@ -14473,6 +14445,7 @@ 2CA9D9812010A0CD007AA743 /* ProgressTableViewCell.swift in Sources */, 084070801D64847000308FC1 /* SharingHelper.swift in Sources */, 2C4436B321356D960084489C /* CourseSubscriber.swift in Sources */, + 0848BE8B218379A400762DC1 /* JoinCourseStringSplitTest.swift in Sources */, 0806B2C21EBDBB0200FDE0F7 /* PostViewsExecutableTask.swift in Sources */, 084156951BCBFFBD006B8C73 /* Step+CoreDataProperties.swift in Sources */, 080E80F51F0070C900DC0EA5 /* CodeLanguages.swift in Sources */, @@ -14481,7 +14454,6 @@ 08E542091C6CD65500DEC38E /* CourseSubscriptionManager.swift in Sources */, 082799481E9B81AF008A3786 /* RateAppAlertManager.swift in Sources */, 08F485AF1C58E946000165AA /* SortingDataset.swift in Sources */, - 08FF75301DF9B89000D656C4 /* StreaksStepikAlertManager.swift in Sources */, 2CF1B33E2163BE720008DA0C /* StoriesAssembly.swift in Sources */, 9F9C715A1E076B3E00EC8DA3 /* PlaybackCommandEntity.swift in Sources */, 083AE48320BD72CA00102FE4 /* PersonalDeadlineManager.swift in Sources */, @@ -14500,7 +14472,6 @@ 080C5E671EFC07C10036EB3D /* CodeQuizViewController.swift in Sources */, 2CAD8B9D2170CDB4003F420B /* LocalNotificationContentProvider.swift in Sources */, 86B457031E9F984800D31850 /* RecommendationsAPI.swift in Sources */, - 083554291CC8FD2D004C4E85 /* NotificationRegistrator.swift in Sources */, 08DF1D921BDAB93900BA35EA /* StringExtensions.swift in Sources */, 08FA62222121BEF900F00275 /* GrowPresentAnimationController.swift in Sources */, 083F2B2A1E9EC17F00714173 /* LoadingPaginationView.swift in Sources */, @@ -14524,6 +14495,7 @@ 083AABE81BE8D63D005E1E96 /* Progress+CoreDataProperties.swift in Sources */, 087585B71FB51D840047A269 /* CourseList+CoreDataProperties.swift in Sources */, 0805FE441F0D390B001226B4 /* CodePlaygroundManager.swift in Sources */, + 2CA3DAA32179C71500F43888 /* NotificationPermissionStatus.swift in Sources */, 2C6B2F9C20D7F24800F7C976 /* AchievementPopupViewController.swift in Sources */, 08D035211D65A252003515C6 /* AnalyticsEvents.swift in Sources */, 084F7AA71E76EF690088368A /* LastStep.swift in Sources */, @@ -14554,7 +14526,6 @@ 08A3A9CE1BD5A14D0032C36E /* NSDateExtensions.swift in Sources */, 08C9F7D01C29AE8B00E544D0 /* WebControllerManager.swift in Sources */, 080CE15B1E95804C0089A27F /* SearchResultsAPI.swift in Sources */, - 08DE94171B8C58AC00D278AB /* AppDelegate.swift in Sources */, 080F74611BD8159F0064AAEA /* TabsInfo.swift in Sources */, 0846B1071EDDED4400D64D77 /* StepOptions+CoreDataProperties.swift in Sources */, 08F4CB281CF5C58C00B15B3D /* WebViewHorizontalScrollHelper.swift in Sources */, @@ -14563,6 +14534,7 @@ 08407EC71DE4891D0082C4E7 /* FBSocialSDKProvider.swift in Sources */, 089574561E5B76E700C12D21 /* UIImageView+SVGDownload.swift in Sources */, 08E59ABF1E433057008EEECE /* FillBlanksDataset.swift in Sources */, + 2C2F0BEE2186F196007DCA0A /* NotificationsRequestAlertDataSource.swift in Sources */, 2C453398204D46E90061342A /* PinsMap.swift in Sources */, 0899842C1ECDE194005C0B27 /* LessonPresenter.swift in Sources */, 9F5F3D711E08783E00000423 /* CourseMetainfoEntity.swift in Sources */, @@ -14581,13 +14553,11 @@ 08484F05211AF4320006266F /* StoryAssembly.swift in Sources */, 089587F520A20C5D003BAFB3 /* Enrollment.swift in Sources */, 2CD9B9681F87A58B00D446C2 /* NotificationDataExtractor.swift in Sources */, - 081566251F69C3AD0082B359 /* CustomSearchBar.swift in Sources */, 2CA1001421749877003775CB /* NotificationService+PersonalDeadline.swift in Sources */, 08CBA3011F57459800302154 /* MenuUIManager.swift in Sources */, 089877B221404CF10065DFA2 /* StringStorageServiceProtocol.swift in Sources */, 2C2365E421078CEF00C99742 /* VideoFileManager.swift in Sources */, 081D7418211DB4720086F6F8 /* OpenedStoriesPageViewController.swift in Sources */, - 08CCAEC1204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */, 08AC21451CDD493A00FBB9CD /* PersistentRecoveryManager.swift in Sources */, 08F4859C1C5786DE000165AA /* StringQuizViewController.swift in Sources */, 087296DC20168FF2009F9256 /* TooltipDefaultsManager.swift in Sources */, @@ -14601,6 +14571,7 @@ 083F2B1D1E9D9ABB00714173 /* CertificateViewData.swift in Sources */, 081A7DA91C56625F00583728 /* QuizViewController.swift in Sources */, 08F485A71C57B023000165AA /* FreeAnswerQuizViewController.swift in Sources */, + 2C82D51721830DD500C10805 /* NotificationsRegistrationServiceProtocol.swift in Sources */, 08F485B31C58EC33000165AA /* SortingQuizViewController.swift in Sources */, 2CE3BCA71FBF13CE000AD405 /* SQLReply.swift in Sources */, 0885F8561BA9F18900F2A188 /* Parser.swift in Sources */, @@ -14629,7 +14600,6 @@ 087387D81BB9A4BD003CFAD1 /* CoreDataHelper.swift in Sources */, 0828FF861BC81EEC000AFEA7 /* UnitsViewController.swift in Sources */, 2CC351971F683140004255B6 /* EmailAuthViewController.swift in Sources */, - 082A88FE2046F0460079F038 /* NotificationPermissionManager.swift in Sources */, 08D9E98C206C243D002F41D3 /* DatabaseFetchService.swift in Sources */, 082BE3AE1E676373006BC60F /* AuthRoutingManager.swift in Sources */, 089056111E98021000B8FE6A /* RateAppViewController.swift in Sources */, @@ -14656,10 +14626,10 @@ 08D035251D65B5E5003515C6 /* AnalyticsReporter.swift in Sources */, 0899842F1ECDE19E005C0B27 /* LessonView.swift in Sources */, 0891424B1BCEE4EF0000BCB0 /* VideoURL.swift in Sources */, + 2C0176C12188953700DDB9D0 /* NotificationAlertsAnalytics.swift in Sources */, 080EBA371EA64C0C00C43C93 /* CertificatesPresentationContainer.swift in Sources */, 089877A0214047650065DFA2 /* SplitTestGroupProtocol.swift in Sources */, 088E73EA2060124B00D458E3 /* ApiRequestRetrier.swift in Sources */, - 08421BC921764C9600E8A81B /* AuthAfterOnboardingSplitTest.swift in Sources */, 2CF08864205BEF3C00FCB9C0 /* StepikPlaceholderView.swift in Sources */, 2C6E9CDB1FF27543001821A2 /* AdaptiveStorageManager.swift in Sources */, 08EB85F01D101D7800E4F345 /* WriteCommentViewController.swift in Sources */, @@ -14682,6 +14652,7 @@ 2C9BD78E1FC43C6B00F89CBE /* NotificationsBadgesManager.swift in Sources */, 08F485AA1C580D61000165AA /* MathReply.swift in Sources */, 08E43E99214C279700E3CB50 /* PushRouterSourceProtocol.swift in Sources */, + 2C2F0BE72186EEB8007DCA0A /* StreakNotificationsRequestAlertDataSource.swift in Sources */, 082B54D520AA290800144817 /* PersonalDeadlinesDefaultsContainer.swift in Sources */, 2CB62BDB2019ECB800B5E336 /* OnboardingCardStepViewController.swift in Sources */, 08E7CA1020DAF6E0004F8563 /* AnalyticsUserProperties.swift in Sources */, @@ -14737,6 +14708,7 @@ 2C97E00F215E482B005684A1 /* SearchResultsViewController.swift in Sources */, 089142491BCEE4EF0000BCB0 /* Video.swift in Sources */, 2CA5E5A7200E1FCA00CE77B0 /* AdaptiveRatingManager.swift in Sources */, + 2C2F0BE92186EF87007DCA0A /* CommonNotificationsRequestAlertDataSource.swift in Sources */, 086BE2851F901CCF00B4BE56 /* LastStepRouter.swift in Sources */, 08B5E29A1BF509C900B875E6 /* UIThreadHelper.swift in Sources */, 2C15EB961FC70A0300F56D93 /* UICollectionViewFlowLayout+PlusCrashWorkaround.swift in Sources */, @@ -14762,7 +14734,6 @@ 0828FF831BC800C0000AFEA7 /* JSONSerializable.swift in Sources */, 08CA59F41BC020E3008DC44D /* UIImageViewExtension.swift in Sources */, 086D5B3F20127A25000F7715 /* Tooltip.swift in Sources */, - 0837492A1DE5AF8A00144C14 /* StreaksAlertManager.swift in Sources */, 0828FF801BC7FD24000AFEA7 /* Lesson+CoreDataProperties.swift in Sources */, 080CE1491E9562430089A27F /* LessonsAPI.swift in Sources */, 2C80C82A201A505E00ABB312 /* CongratulationViewController.swift in Sources */, @@ -15030,6 +15001,7 @@ 62E987FD412017016C6AD457 /* HighlightFakeButton.swift in Sources */, 62E982769C19C7852BCD59C4 /* TooltipStorageManager.swift in Sources */, 62E985DD90444821A835C02F /* CourseListsCollectionSkeletonView.swift in Sources */, + 62E98A53567525055BCCA089 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -15308,7 +15280,6 @@ 2C1B60DA1F4C4AEF00236804 /* RatingProgressView.swift in Sources */, 2C1B60DC1F4C4AEF00236804 /* RateAppViewController.swift in Sources */, 08DF78D81F64059900AEEA85 /* StepikLabel.swift in Sources */, - 0815662B1F69C3AD0082B359 /* CustomSearchBar.swift in Sources */, 2C1B60DE1F4C4AEF00236804 /* LaunchDefaultsContainer.swift in Sources */, 2C1B60DF1F4C4AEF00236804 /* DefaultsContainer.swift in Sources */, 2C1B60E01F4C4AEF00236804 /* CoursesAPI.swift in Sources */, @@ -15499,7 +15470,6 @@ 2C1B619C1F4C4AEF00236804 /* CodePlaygroundManager.swift in Sources */, 2C1B619D1F4C4AEF00236804 /* Scripts.swift in Sources */, 2C1B619E1F4C4AEF00236804 /* Scripts.plist in Sources */, - 08CCAEC6204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */, 2C1B619F1F4C4AEF00236804 /* LocalNotificationsHelper.swift in Sources */, 2C1B61A01F4C4AEF00236804 /* Constants.swift in Sources */, 2C1B61A11F4C4AEF00236804 /* Images.swift in Sources */, @@ -15518,7 +15488,7 @@ 2C1B61AE1F4C4AEF00236804 /* VersionUpdateAlertConstructor.swift in Sources */, 2C1B61AF1F4C4AEF00236804 /* Version.swift in Sources */, 86A1C334206515B300D0914C /* ControllerWithStepikPlaceholder.swift in Sources */, - 2C1B61B01F4C4AEF00236804 /* StreaksNotificationSuggestionManager.swift in Sources */, + 2C1B61B01F4C4AEF00236804 /* NotificationSuggestionManager.swift in Sources */, 2C1B61B21F4C4AEF00236804 /* Executable.swift in Sources */, 2C1B61B31F4C4AEF00236804 /* ExecutionQueue.swift in Sources */, 2C1B61B41F4C4AEF00236804 /* PersistentQueueRecoveryManager.swift in Sources */, @@ -15543,7 +15513,6 @@ 087585C61FB524C20047A269 /* ContentLanguage.swift in Sources */, 2C1B61C51F4C4AEF00236804 /* RoutingManager.swift in Sources */, 2C608AD3204568E2006870BB /* ProgressTableViewCell.swift in Sources */, - 2C1B61C61F4C4AEF00236804 /* NotificationRegistrator.swift in Sources */, 2C1B61C71F4C4AEF00236804 /* NotificationAlertConstructor.swift in Sources */, 2C1B61C81F4C4AEF00236804 /* DeviceDefaults.swift in Sources */, 08DF78A41F5DE64300AEEA85 /* ArtView.swift in Sources */, @@ -15574,7 +15543,6 @@ 08EA89D5200E69CB00BCAF44 /* SQLQuizViewController.swift in Sources */, 2C1B61DE1F4C4AEF00236804 /* MathReply.swift in Sources */, 2C1B61DF1F4C4AEF00236804 /* SortingReply.swift in Sources */, - 082A89032046F0460079F038 /* NotificationPermissionManager.swift in Sources */, 2C1B61E01F4C4AEF00236804 /* MatchingReply.swift in Sources */, 2C1B61E11F4C4AEF00236804 /* FillBlanksReply.swift in Sources */, 2C1B61E21F4C4AEF00236804 /* VideoDownloadDelegate.swift in Sources */, @@ -15663,10 +15631,8 @@ 2CF9532A2062A20A00B9617A /* StepikPlaceholderView.swift in Sources */, 2C1B62291F4C4AEF00236804 /* UserActivity.swift in Sources */, 2C1B622A1F4C4AEF00236804 /* Alerts.swift in Sources */, - 2C1B622B1F4C4AEF00236804 /* StreaksAlertManager.swift in Sources */, 2C1B622C1F4C4AEF00236804 /* QuizPresenter.swift in Sources */, 2C608AE3204568F9006870BB /* AdaptiveStatsViewController.swift in Sources */, - 2C1B622D1F4C4AEF00236804 /* StreaksStepikAlertManager.swift in Sources */, 2C1B622E1F4C4AEF00236804 /* InputAccessoryBuilder.swift in Sources */, 2C1B622F1F4C4AEF00236804 /* AlertManager.swift in Sources */, 08D5F5731F7DA6BB007C1634 /* CourseReviewSummary.swift in Sources */, @@ -15754,7 +15720,6 @@ 2C1B62EB1F4C590700236804 /* RatingProgressView.swift in Sources */, 2C1B62ED1F4C590700236804 /* RateAppViewController.swift in Sources */, 08DF78D91F64059900AEEA85 /* StepikLabel.swift in Sources */, - 0815662C1F69C3AD0082B359 /* CustomSearchBar.swift in Sources */, 2C1B62EF1F4C590700236804 /* LaunchDefaultsContainer.swift in Sources */, 2C1B62F01F4C590700236804 /* DefaultsContainer.swift in Sources */, 2C1B62F11F4C590700236804 /* CoursesAPI.swift in Sources */, @@ -15945,7 +15910,6 @@ 2C1B63AD1F4C590700236804 /* CodePlaygroundManager.swift in Sources */, 2C1B63AE1F4C590700236804 /* Scripts.swift in Sources */, 2C1B63AF1F4C590700236804 /* Scripts.plist in Sources */, - 08CCAEC7204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */, 2C1B63B01F4C590700236804 /* LocalNotificationsHelper.swift in Sources */, 2C1B63B11F4C590700236804 /* Constants.swift in Sources */, 2C1B63B21F4C590700236804 /* Images.swift in Sources */, @@ -15964,7 +15928,7 @@ 2C1B63BF1F4C590700236804 /* VersionUpdateAlertConstructor.swift in Sources */, 2C1B63C01F4C590700236804 /* Version.swift in Sources */, 86A1C335206515B400D0914C /* ControllerWithStepikPlaceholder.swift in Sources */, - 2C1B63C11F4C590700236804 /* StreaksNotificationSuggestionManager.swift in Sources */, + 2C1B63C11F4C590700236804 /* NotificationSuggestionManager.swift in Sources */, 2C1B63C31F4C590700236804 /* Executable.swift in Sources */, 2C1B63C41F4C590700236804 /* ExecutionQueue.swift in Sources */, 2C1B63C51F4C590700236804 /* PersistentQueueRecoveryManager.swift in Sources */, @@ -15989,7 +15953,6 @@ 087585C71FB524C20047A269 /* ContentLanguage.swift in Sources */, 2C1B63D61F4C590700236804 /* RoutingManager.swift in Sources */, 2C608AD2204568E2006870BB /* ProgressTableViewCell.swift in Sources */, - 2C1B63D71F4C590700236804 /* NotificationRegistrator.swift in Sources */, 2C1B63D81F4C590700236804 /* NotificationAlertConstructor.swift in Sources */, 2C1B63D91F4C590700236804 /* DeviceDefaults.swift in Sources */, 08DF78A51F5DE64300AEEA85 /* ArtView.swift in Sources */, @@ -16020,7 +15983,6 @@ 08EA89D6200E69CC00BCAF44 /* SQLQuizViewController.swift in Sources */, 2C1B63EF1F4C590700236804 /* MathReply.swift in Sources */, 2C1B63F01F4C590700236804 /* SortingReply.swift in Sources */, - 082A89042046F0460079F038 /* NotificationPermissionManager.swift in Sources */, 2C1B63F11F4C590700236804 /* MatchingReply.swift in Sources */, 2C1B63F21F4C590700236804 /* FillBlanksReply.swift in Sources */, 2C1B63F31F4C590700236804 /* VideoDownloadDelegate.swift in Sources */, @@ -16109,10 +16071,8 @@ 2CF953292062A20A00B9617A /* StepikPlaceholderView.swift in Sources */, 2C1B643A1F4C590700236804 /* UserActivity.swift in Sources */, 2C1B643B1F4C590700236804 /* Alerts.swift in Sources */, - 2C1B643C1F4C590700236804 /* StreaksAlertManager.swift in Sources */, 2C1B643D1F4C590700236804 /* QuizPresenter.swift in Sources */, 2C608AE1204568F8006870BB /* AdaptiveStatsViewController.swift in Sources */, - 2C1B643E1F4C590700236804 /* StreaksStepikAlertManager.swift in Sources */, 2C1B643F1F4C590700236804 /* InputAccessoryBuilder.swift in Sources */, 2C1B64401F4C590700236804 /* AlertManager.swift in Sources */, 08D5F5741F7DA6BB007C1634 /* CourseReviewSummary.swift in Sources */, @@ -16409,7 +16369,6 @@ 2C770301210F29A3002E250D /* KnowledgeGraphTopicsMapPlainObject.swift in Sources */, 2C9499B920ECBB150049E9A8 /* Certificate+CoreDataProperties.swift in Sources */, 2CA5568F212339E200A3B1AB /* Constants.swift in Sources */, - 2C9499EC20ECBFED0049E9A8 /* NotificationPermissionManager.swift in Sources */, 2C9492CD2113119100552600 /* StandartStepsAssemblyProtocol.swift in Sources */, 2C9499D720ECBD080049E9A8 /* SQLReply.swift in Sources */, 2C093EF6211B419300162DD0 /* QuizControllerDelegate.swift in Sources */, @@ -16527,12 +16486,10 @@ 2C94999F20ECB9220049E9A8 /* Step+CoreDataProperties.swift in Sources */, 2C33CC0220EC334C009956B0 /* NotificationsAPI.swift in Sources */, 2C681A07210B15C400C3DDCA /* AuthSignUpViewController.swift in Sources */, - 2C093F12211B441600162DD0 /* NotificationRequestAlertManager.swift in Sources */, 2CA3F56120FF7C030041E893 /* GraphService.swift in Sources */, 2C949A3420ECC7120049E9A8 /* AchievementProgress.swift in Sources */, 2C093EF4211B3E3600162DD0 /* QuizViewController.swift in Sources */, 2C093F0E211B43BD00162DD0 /* PlaceholderTableViewCell.swift in Sources */, - 2C9492C921130FC100552600 /* NotificationRegistrator.swift in Sources */, 2C33CBEF20EC3251009956B0 /* UserActivitiesAPI.swift in Sources */, 2C9499DB20ECBD780049E9A8 /* UserActivity.swift in Sources */, 2C67B22120FF1A93005E699B /* AuthSignUpAssemblyImpl.swift in Sources */, @@ -16586,7 +16543,7 @@ 2C33CBDD20EC2E50009956B0 /* DefaultsStorageManager.swift in Sources */, 2C9499A120ECB9430049E9A8 /* WatchSessionManager.swift in Sources */, 2C9499F420ECC0C40049E9A8 /* ExecutableTaskTypes.swift in Sources */, - 2C093EF7211B41D500162DD0 /* StreaksNotificationSuggestionManager.swift in Sources */, + 2C093EF7211B41D500162DD0 /* NotificationSuggestionManager.swift in Sources */, 2C949A2820ECC6240049E9A8 /* PinsMapBlockContentView.swift in Sources */, 2C949A5D20ECCEA70049E9A8 /* LabelExtension.swift in Sources */, 2C9499A820ECB9FB0049E9A8 /* Dataset.swift in Sources */, @@ -16641,7 +16598,6 @@ 62E987EE86AB48E30FF5EC79 /* ProgressServiceImpl.swift in Sources */, 62E989D0D1B162A9AE12DFD6 /* StepsServiceImpl.swift in Sources */, 2C093F04211B42F500162DD0 /* ProfileViewController.swift in Sources */, - 2C093F10211B43FE00162DD0 /* StreaksStepikAlertManager.swift in Sources */, 62E98D2289B2FF8307945FAA /* StepsPagerRouterImpl.swift in Sources */, 62E989D5A545A5CD3C44DB8B /* CAGradientLayer+Locations.swift in Sources */, ); @@ -16744,7 +16700,6 @@ 2C89A9701F4C289900227C3B /* RateAppViewController.swift in Sources */, 08DF78D61F64059900AEEA85 /* StepikLabel.swift in Sources */, 2C6A76372045CE7B00C509A6 /* CardStepViewController.swift in Sources */, - 081566291F69C3AD0082B359 /* CustomSearchBar.swift in Sources */, 2C89A9721F4C289900227C3B /* LaunchDefaultsContainer.swift in Sources */, 2C89A9731F4C289900227C3B /* DefaultsContainer.swift in Sources */, 2C89A9741F4C289900227C3B /* CoursesAPI.swift in Sources */, @@ -16899,7 +16854,6 @@ 2C89AA191F4C289900227C3B /* Time.swift in Sources */, 2C89AA1A1F4C289900227C3B /* AnalyticsHelper.swift in Sources */, 2C89AA1B1F4C289900227C3B /* ControllerHelper.swift in Sources */, - 08CCAEC4204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */, 2C89AA1C1F4C289900227C3B /* GCDThings.swift in Sources */, 087296E020168FF2009F9256 /* TooltipDefaultsManager.swift in Sources */, 2C89AA1D1F4C289900227C3B /* GlobalFunctions.swift in Sources */, @@ -16951,10 +16905,9 @@ 2C89AA411F4C289900227C3B /* UpdatePreferencesContainer.swift in Sources */, 2C89AA421F4C289900227C3B /* VersionUpdateAlertConstructor.swift in Sources */, 2C89AA431F4C289900227C3B /* Version.swift in Sources */, - 2C89AA441F4C289900227C3B /* StreaksNotificationSuggestionManager.swift in Sources */, + 2C89AA441F4C289900227C3B /* NotificationSuggestionManager.swift in Sources */, 2C89AA461F4C289900227C3B /* Executable.swift in Sources */, 2C89AA471F4C289900227C3B /* ExecutionQueue.swift in Sources */, - 082A89012046F0460079F038 /* NotificationPermissionManager.swift in Sources */, 2C89AA481F4C289900227C3B /* PersistentQueueRecoveryManager.swift in Sources */, 2C89AA491F4C289900227C3B /* Queues.plist in Sources */, 2C89AA4A1F4C289900227C3B /* ExecutionQueues.swift in Sources */, @@ -16975,7 +16928,6 @@ 2C89AA571F4C289900227C3B /* PersistentTaskManagerProtocol.swift in Sources */, 2C89AA581F4C289900227C3B /* SearchQueriesPresenter.swift in Sources */, 2C89AA591F4C289900227C3B /* RoutingManager.swift in Sources */, - 2C89AA5A1F4C289900227C3B /* NotificationRegistrator.swift in Sources */, 08EA89D3200E69CA00BCAF44 /* SQLQuizViewController.swift in Sources */, 2C89AA5B1F4C289900227C3B /* NotificationAlertConstructor.swift in Sources */, 2C6A76572045CFDD00C509A6 /* UICollectionViewFlowLayout+PlusCrashWorkaround.swift in Sources */, @@ -17097,9 +17049,7 @@ 2C89AABC1F4C289900227C3B /* StepsControllerDeepLinkRouter.swift in Sources */, 2C89AABD1F4C289900227C3B /* UserActivity.swift in Sources */, 2C89AABE1F4C289900227C3B /* Alerts.swift in Sources */, - 2C89AABF1F4C289900227C3B /* StreaksAlertManager.swift in Sources */, 2C89AAC01F4C289900227C3B /* QuizPresenter.swift in Sources */, - 2C89AAC11F4C289900227C3B /* StreaksStepikAlertManager.swift in Sources */, 2C89AAC21F4C289900227C3B /* InputAccessoryBuilder.swift in Sources */, 086D5B56201283C2000F7715 /* TooltipFactory.swift in Sources */, 2C89AAC31F4C289900227C3B /* AlertManager.swift in Sources */, @@ -17191,7 +17141,6 @@ 2C9731221F4C38F600AC9301 /* RatingProgressView.swift in Sources */, 2C9731241F4C38F600AC9301 /* RateAppViewController.swift in Sources */, 08DF78D71F64059900AEEA85 /* StepikLabel.swift in Sources */, - 0815662A1F69C3AD0082B359 /* CustomSearchBar.swift in Sources */, 2C9731261F4C38F600AC9301 /* LaunchDefaultsContainer.swift in Sources */, 2C9731271F4C38F600AC9301 /* DefaultsContainer.swift in Sources */, 2C9731281F4C38F600AC9301 /* CoursesAPI.swift in Sources */, @@ -17379,7 +17328,6 @@ 2C9731E41F4C38F600AC9301 /* CodePlaygroundManager.swift in Sources */, 2C9731E51F4C38F600AC9301 /* Scripts.swift in Sources */, 2C9731E61F4C38F600AC9301 /* Scripts.plist in Sources */, - 08CCAEC5204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */, 2C9731E71F4C38F600AC9301 /* LocalNotificationsHelper.swift in Sources */, 2C9731E81F4C38F600AC9301 /* Constants.swift in Sources */, 2C9731E91F4C38F600AC9301 /* Images.swift in Sources */, @@ -17397,7 +17345,7 @@ 2C9731F51F4C38F600AC9301 /* UpdatePreferencesContainer.swift in Sources */, 2C9731F61F4C38F600AC9301 /* VersionUpdateAlertConstructor.swift in Sources */, 2C9731F71F4C38F600AC9301 /* Version.swift in Sources */, - 2C9731F81F4C38F600AC9301 /* StreaksNotificationSuggestionManager.swift in Sources */, + 2C9731F81F4C38F600AC9301 /* NotificationSuggestionManager.swift in Sources */, 86A1C333206515B300D0914C /* ControllerWithStepikPlaceholder.swift in Sources */, 2C9731FA1F4C38F600AC9301 /* Executable.swift in Sources */, 2C9731FB1F4C38F600AC9301 /* ExecutionQueue.swift in Sources */, @@ -17424,7 +17372,6 @@ 2CBC261721086948004245D6 /* UserRegistrationServiceImpl.swift in Sources */, 2C97320D1F4C38F600AC9301 /* RoutingManager.swift in Sources */, 2C608AD4204568E2006870BB /* ProgressTableViewCell.swift in Sources */, - 2C97320E1F4C38F600AC9301 /* NotificationRegistrator.swift in Sources */, 2C97320F1F4C38F600AC9301 /* NotificationAlertConstructor.swift in Sources */, 2C9732101F4C38F600AC9301 /* DeviceDefaults.swift in Sources */, 08DF78A31F5DE64300AEEA85 /* ArtView.swift in Sources */, @@ -17455,7 +17402,6 @@ 08EA89D4200E69CB00BCAF44 /* SQLQuizViewController.swift in Sources */, 2C9732261F4C38F600AC9301 /* MathReply.swift in Sources */, 2C9732271F4C38F600AC9301 /* SortingReply.swift in Sources */, - 082A89022046F0460079F038 /* NotificationPermissionManager.swift in Sources */, 2C9732281F4C38F600AC9301 /* MatchingReply.swift in Sources */, 2C9732291F4C38F600AC9301 /* FillBlanksReply.swift in Sources */, 2C97322A1F4C38F600AC9301 /* VideoDownloadDelegate.swift in Sources */, @@ -17547,10 +17493,8 @@ 2CF9532B2062A20B00B9617A /* StepikPlaceholderView.swift in Sources */, 2C9732711F4C38F600AC9301 /* UserActivity.swift in Sources */, 2C9732721F4C38F600AC9301 /* Alerts.swift in Sources */, - 2C9732731F4C38F600AC9301 /* StreaksAlertManager.swift in Sources */, 2C9732741F4C38F600AC9301 /* QuizPresenter.swift in Sources */, 2C608AE2204568F9006870BB /* AdaptiveStatsViewController.swift in Sources */, - 2C9732751F4C38F600AC9301 /* StreaksStepikAlertManager.swift in Sources */, 2C9732761F4C38F600AC9301 /* InputAccessoryBuilder.swift in Sources */, 2C9732771F4C38F600AC9301 /* AlertManager.swift in Sources */, 08D5F5721F7DA6BB007C1634 /* CourseReviewSummary.swift in Sources */, @@ -17651,7 +17595,6 @@ 2CA8D15E2088D9C000E105E9 /* RateAppViewController.swift in Sources */, 2CA8D15F2088D9C000E105E9 /* StepikLabel.swift in Sources */, 2CA8D1602088D9C000E105E9 /* CardStepViewController.swift in Sources */, - 2CA8D1612088D9C000E105E9 /* CustomSearchBar.swift in Sources */, 2CA8D1622088D9C000E105E9 /* LaunchDefaultsContainer.swift in Sources */, 2CA8D1642088D9C000E105E9 /* DefaultsContainer.swift in Sources */, 2CA8D1652088D9C000E105E9 /* CoursesAPI.swift in Sources */, @@ -17802,7 +17745,6 @@ 2CA8D2132088D9C000E105E9 /* Time.swift in Sources */, 2CA8D2142088D9C000E105E9 /* AnalyticsHelper.swift in Sources */, 2CA8D2152088D9C000E105E9 /* ControllerHelper.swift in Sources */, - 2CA8D2162088D9C000E105E9 /* NotificationRequestAlertManager.swift in Sources */, 2CA8D2172088D9C000E105E9 /* GCDThings.swift in Sources */, 2CA8D2182088D9C000E105E9 /* TooltipDefaultsManager.swift in Sources */, 2CA8D21A2088D9C000E105E9 /* GlobalFunctions.swift in Sources */, @@ -17854,10 +17796,9 @@ 2CA8D24D2088D9C000E105E9 /* UpdatePreferencesContainer.swift in Sources */, 2CA8D24E2088D9C000E105E9 /* VersionUpdateAlertConstructor.swift in Sources */, 2CA8D24F2088D9C000E105E9 /* Version.swift in Sources */, - 2CA8D2502088D9C000E105E9 /* StreaksNotificationSuggestionManager.swift in Sources */, + 2CA8D2502088D9C000E105E9 /* NotificationSuggestionManager.swift in Sources */, 2CA8D2512088D9C000E105E9 /* Executable.swift in Sources */, 2CA8D2522088D9C000E105E9 /* ExecutionQueue.swift in Sources */, - 2CA8D2532088D9C000E105E9 /* NotificationPermissionManager.swift in Sources */, 2CA8D2542088D9C000E105E9 /* PersistentQueueRecoveryManager.swift in Sources */, 2CA8D2552088D9C000E105E9 /* Queues.plist in Sources */, 2CA8D2562088D9C000E105E9 /* ExecutionQueues.swift in Sources */, @@ -17880,7 +17821,6 @@ 2CA8D2662088D9C000E105E9 /* PersistentTaskManagerProtocol.swift in Sources */, 2CA8D2672088D9C000E105E9 /* SearchQueriesPresenter.swift in Sources */, 2CA8D2682088D9C000E105E9 /* RoutingManager.swift in Sources */, - 2CA8D2692088D9C000E105E9 /* NotificationRegistrator.swift in Sources */, 2CA8D26A2088D9C000E105E9 /* SQLQuizViewController.swift in Sources */, 2CA8D26B2088D9C000E105E9 /* NotificationAlertConstructor.swift in Sources */, 2CA8D26C2088D9C000E105E9 /* UICollectionViewFlowLayout+PlusCrashWorkaround.swift in Sources */, @@ -18002,9 +17942,7 @@ 2CA8D2E82088D9C000E105E9 /* StepsControllerDeepLinkRouter.swift in Sources */, 2CA8D2E92088D9C000E105E9 /* UserActivity.swift in Sources */, 2CA8D2EA2088D9C000E105E9 /* Alerts.swift in Sources */, - 2CA8D2EC2088D9C000E105E9 /* StreaksAlertManager.swift in Sources */, 2CA8D2ED2088D9C000E105E9 /* QuizPresenter.swift in Sources */, - 2CA8D2EE2088D9C000E105E9 /* StreaksStepikAlertManager.swift in Sources */, 2CA8D2EF2088D9C000E105E9 /* InputAccessoryBuilder.swift in Sources */, 2CA8D2F02088D9C000E105E9 /* TooltipFactory.swift in Sources */, 2CA1001C2174BDEC003775CB /* NotificationService+Streak.swift in Sources */, @@ -18165,7 +18103,6 @@ 2C76ACD11F18E8C10077D9D7 /* RatingProgressView.swift in Sources */, 864514E51E9FD4F6007F73BE /* RateAppViewController.swift in Sources */, 08DF78D41F64059900AEEA85 /* StepikLabel.swift in Sources */, - 081566271F69C3AD0082B359 /* CustomSearchBar.swift in Sources */, 864514E71E9FD4F6007F73BE /* LaunchDefaultsContainer.swift in Sources */, 864514E81E9FD4F6007F73BE /* DefaultsContainer.swift in Sources */, 864514E91E9FD4F6007F73BE /* CoursesAPI.swift in Sources */, @@ -18260,7 +18197,6 @@ 08A9F7201FC38C9E00640F1F /* CourseTag.swift in Sources */, 086442B61F764F68000CC53D /* AuthNavigationViewController.swift in Sources */, 2CE527C22029D9030047EC5F /* AdaptiveStatsSection.swift in Sources */, - 08CCAEC2204F0B2D002B4544 /* NotificationRequestAlertManager.swift in Sources */, 0846B11B1EDDF64F00D64D77 /* CodeTemplate+CoreDataProperties.swift in Sources */, 2C733C3A1F29E090000E7FAF /* AdaptiveStatsManager.swift in Sources */, 2C3A22552121DEFD00927BD7 /* AdaptiveCourseSelectPresenter.swift in Sources */, @@ -18370,7 +18306,7 @@ 864D66BB1E83DE03001E8D9E /* VersionUpdateAlertConstructor.swift in Sources */, 082E5E101F46379100F41426 /* ReplyCache.swift in Sources */, 864D66BC1E83DE03001E8D9E /* Version.swift in Sources */, - 864D66BD1E83DE03001E8D9E /* StreaksNotificationSuggestionManager.swift in Sources */, + 864D66BD1E83DE03001E8D9E /* NotificationSuggestionManager.swift in Sources */, 864D66BF1E83DE03001E8D9E /* Executable.swift in Sources */, 864D66C01E83DE03001E8D9E /* ExecutionQueue.swift in Sources */, 085EDCB91F9945D900AB3278 /* Notification+CoreDataProperties.swift in Sources */, @@ -18399,7 +18335,6 @@ 087585C21FB524C20047A269 /* ContentLanguage.swift in Sources */, 08E8F9691F34DD2C008CF4A1 /* SearchQueriesPresenter.swift in Sources */, 08E3A3B71E93C21300E9C2EF /* RoutingManager.swift in Sources */, - 864D66CD1E83DE03001E8D9E /* NotificationRegistrator.swift in Sources */, 864D66CE1E83DE03001E8D9E /* NotificationAlertConstructor.swift in Sources */, 864D66CF1E83DE03001E8D9E /* DeviceDefaults.swift in Sources */, 08DF78A01F5DE64300AEEA85 /* ArtView.swift in Sources */, @@ -18474,7 +18409,6 @@ 864D67011E83DE03001E8D9E /* Session.swift in Sources */, 864D67021E83DE03001E8D9E /* SocialSDKProvider.swift in Sources */, 864D67031E83DE03001E8D9E /* VKSocialSDKProvider.swift in Sources */, - 082A88FF2046F0460079F038 /* NotificationPermissionManager.swift in Sources */, 864D67041E83DE03001E8D9E /* FBSocialSDKProvider.swift in Sources */, 864D67061E83DE03001E8D9E /* ApiRequest.swift in Sources */, 864D67071E83DE03001E8D9E /* AuthAPI.swift in Sources */, @@ -18513,12 +18447,10 @@ 864D671D1E83DE03001E8D9E /* StepsControllerDeepLinkRouter.swift in Sources */, 864D671E1E83DE03001E8D9E /* UserActivity.swift in Sources */, 864D671F1E83DE03001E8D9E /* Alerts.swift in Sources */, - 864D67201E83DE03001E8D9E /* StreaksAlertManager.swift in Sources */, 2CE527C82029E1600047EC5F /* AdaptiveAchievementsViewController.swift in Sources */, 08C1FC351F41E74500E14B46 /* QuizPresenter.swift in Sources */, 2CA100192174BDEC003775CB /* NotificationService+Streak.swift in Sources */, 2CA2E72420237E4D001DC410 /* AdaptiveStatsViewController.swift in Sources */, - 864D67211E83DE03001E8D9E /* StreaksStepikAlertManager.swift in Sources */, 085E9E6C1F138C1D00D6A4BC /* InputAccessoryBuilder.swift in Sources */, 864D67221E83DE03001E8D9E /* AlertManager.swift in Sources */, 08D5F56F1F7DA6BB007C1634 /* CourseReviewSummary.swift in Sources */, @@ -19104,7 +19036,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 107; + CURRENT_PROJECT_VERSION = 109; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; INFOPLIST_FILE = Stepic/Info.plist; @@ -19135,7 +19067,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 107; + CURRENT_PROJECT_VERSION = 109; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; INFOPLIST_FILE = Stepic/Info.plist; diff --git a/Stepic/ActiveSplitTestsContainer.swift b/Stepic/ActiveSplitTestsContainer.swift index a5d29f6e40..917ad9e914 100644 --- a/Stepic/ActiveSplitTestsContainer.swift +++ b/Stepic/ActiveSplitTestsContainer.swift @@ -12,6 +12,6 @@ class ActiveSplitTestsContainer { private static let splitTestingService = SplitTestingService(analyticsService: AnalyticsUserProperties(), storage: UserDefaults.standard) static func setActiveTestsGroups() { - splitTestingService.fetchSplitTest(AuthAfterOnboardingSplitTest.self).setSplitTestGroup() + splitTestingService.fetchSplitTest(JoinCourseStringSplitTest.self).setSplitTestGroup() } } diff --git a/Stepic/Alerts.swift b/Stepic/Alerts.swift index b7215648c3..551ba55e4c 100644 --- a/Stepic/Alerts.swift +++ b/Stepic/Alerts.swift @@ -12,7 +12,5 @@ import Foundation Class, which contains different alert managers */ class Alerts { - static let streaks = StreaksStepikAlertManager() static let rate = RateAppAlertManager() - static let notificationRequest = NotificationRequestAlertManager() } diff --git a/Stepic/AmplitudeAnalyticsEvents.swift b/Stepic/Analytics/AmplitudeAnalyticsEvents.swift similarity index 86% rename from Stepic/AmplitudeAnalyticsEvents.swift rename to Stepic/Analytics/AmplitudeAnalyticsEvents.swift index 2c2d901d68..bffde110f5 100644 --- a/Stepic/AmplitudeAnalyticsEvents.swift +++ b/Stepic/Analytics/AmplitudeAnalyticsEvents.swift @@ -179,6 +179,49 @@ struct AmplitudeAnalyticsEvents { ] ) } + + static func defaultAlertShown(source: String) -> AnalyticsEvent { + return AnalyticsEvent( + name: "Default notification alert shown", + parameters: [ + "source": source + ] + ) + } + + static func defaultAlertInteracted(source: String, result: InteractionResult) -> AnalyticsEvent { + return AnalyticsEvent( + name: "Default notification alert interacted", + parameters: [ + "source": source, + "result": result.rawValue + ] + ) + } + + static func customAlertShown(source: String) -> AnalyticsEvent { + return AnalyticsEvent( + name: "Custom notification alert shown", + parameters: [ + "source": source + ] + ) + } + + static func customAlertInteracted(source: String, result: InteractionResult) -> AnalyticsEvent { + return AnalyticsEvent( + name: "Custom notification alert interacted", + parameters: [ + "source": source, + "result": result.rawValue + ] + ) + } + + enum InteractionResult: String { + case yes + case no + } } struct Home { @@ -302,7 +345,7 @@ struct AmplitudeAnalyticsEvents { static func buttonPressed(id: Int, position: Int) -> AnalyticsEvent { return AnalyticsEvent( - name: "Button pressed", + name: "Story button pressed", parameters: [ "id": id, "position": position diff --git a/Stepic/AnalyticsEvent.swift b/Stepic/Analytics/AnalyticsEvent.swift similarity index 100% rename from Stepic/AnalyticsEvent.swift rename to Stepic/Analytics/AnalyticsEvent.swift diff --git a/Stepic/AnalyticsEvents.swift b/Stepic/Analytics/AnalyticsEvents.swift similarity index 100% rename from Stepic/AnalyticsEvents.swift rename to Stepic/Analytics/AnalyticsEvents.swift diff --git a/Stepic/AnalyticsReporter.swift b/Stepic/Analytics/AnalyticsReporter.swift similarity index 100% rename from Stepic/AnalyticsReporter.swift rename to Stepic/Analytics/AnalyticsReporter.swift diff --git a/Stepic/AnalyticsUserProperties.swift b/Stepic/Analytics/AnalyticsUserProperties.swift similarity index 78% rename from Stepic/AnalyticsUserProperties.swift rename to Stepic/Analytics/AnalyticsUserProperties.swift index 72ddfc1716..c7d1cc7c07 100644 --- a/Stepic/AnalyticsUserProperties.swift +++ b/Stepic/Analytics/AnalyticsUserProperties.swift @@ -62,14 +62,22 @@ class AnalyticsUserProperties: ABAnalyticsServiceProtocol { setProperty(key: "courses_count", value: count) } - //Not supported yet, commented out -// func setPushPermission(isGranted: Bool) { -// setProperty(key: "push_permission", value: isGranted ? "granted" : "not_granted") -// } - -// func setStreaksNotificationsEnabled(isEnabled: Bool) { -// setProperty(key: "streaks_notifications_enabled", value: isEnabled ? "enabled" : "disabled") -// } + func setPushPermissionStatus(_ status: NotificationPermissionStatus) { + let key = "push_permission" + + switch status { + case .authorized: + self.setProperty(key: key, value: "granted") + case .denied: + self.setProperty(key: key, value: "not_granted") + case .notDetermined: + self.setProperty(key: key, value: "not_determined") + } + } + + func setStreaksNotificationsEnabled(_ enabled: Bool) { + self.setProperty(key: "streaks_notifications_enabled", value: enabled ? "enabled" : "disabled") + } func setScreenOrientation(isPortrait: Bool) { setProperty(key: "screen_orientation", value: isPortrait ? "portrait" : "landscape") diff --git a/Stepic/AnalyticsUserPropertiesServiceProtocol.swift b/Stepic/Analytics/AnalyticsUserPropertiesServiceProtocol.swift similarity index 100% rename from Stepic/AnalyticsUserPropertiesServiceProtocol.swift rename to Stepic/Analytics/AnalyticsUserPropertiesServiceProtocol.swift diff --git a/Stepic/Analytics/NotificationAlertsAnalytics.swift b/Stepic/Analytics/NotificationAlertsAnalytics.swift new file mode 100644 index 0000000000..c1c0347f8e --- /dev/null +++ b/Stepic/Analytics/NotificationAlertsAnalytics.swift @@ -0,0 +1,69 @@ +// +// NotificationAlertsAnalytics.swift +// Stepic +// +// Created by Ivan Magda on 30/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation + +struct NotificationAlertsAnalytics { + let source: Source + + func reportDefaultAlertShown() { + AmplitudeAnalyticsEvents.Notifications.defaultAlertShown( + source: self.source.description + ).send() + } + + func reportDefaultAlertInteractionResult( + _ result: AmplitudeAnalyticsEvents.Notifications.InteractionResult + ) { + AmplitudeAnalyticsEvents.Notifications.defaultAlertInteracted( + source: self.source.description, + result: result + ).send() + } + + func reportCustomAlertShown() { + AmplitudeAnalyticsEvents.Notifications.customAlertShown( + source: self.source.description + ).send() + } + + func reportCustomAlertInteractionResult( + _ result: AmplitudeAnalyticsEvents.Notifications.InteractionResult + ) { + AmplitudeAnalyticsEvents.Notifications.customAlertInteracted( + source: self.source.description, + result: result + ).send() + } + + enum Source { + case streakControl + case notificationsTab + case courseSubscription + case streakAfterLogin + case streakAfterSubmission(shownCount: Int) + case personalDeadline + + var description: String { + switch self { + case .streakControl: + return "streak control" + case .notificationsTab: + return "notifications tab" + case .courseSubscription: + return "course subscription" + case .streakAfterLogin: + return "streak after login" + case .streakAfterSubmission(let shownCount): + return "streak after submission - \(shownCount)" + case .personalDeadline: + return "create personal deadline" + } + } + } +} diff --git a/Stepic/AppDelegate.swift b/Stepic/AppDelegate.swift index c1861cecb2..c572b8f1b6 100644 --- a/Stepic/AppDelegate.swift +++ b/Stepic/AppDelegate.swift @@ -26,6 +26,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? private let userNotificationsCenterDelegate = UserNotificationsCenterDelegate() + private let notificationsRegistrationService: NotificationsRegistrationServiceProtocol = NotificationsRegistrationService() deinit { NotificationCenter.default.removeObserver(self) @@ -84,7 +85,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { IQKeyboardManager.sharedManager().enableAutoToolbar = false if !DefaultsContainer.launch.didLaunch { + DefaultsContainer.launch.initStartVersion() ActiveSplitTestsContainer.setActiveTestsGroups() + AnalyticsUserProperties.shared.setPushPermissionStatus(.notDetermined) AnalyticsReporter.reportEvent(AnalyticsEvents.App.firstLaunch, parameters: nil) AmplitudeAnalyticsEvents.Launch.firstTime.send() } @@ -93,10 +96,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { self.checkForUpdates() } - if AuthInfo.shared.isAuthorized { - NotificationRegistrator.shared.registerForRemoteNotificationsIfAlreadyAsked() - } - + self.notificationsRegistrationService.renewDeviceToken() LocalNotificationsMigrator().migrateIfNeeded() NotificationsService().handleLaunchOptions(launchOptions) self.userNotificationsCenterDelegate.attachNotificationDelegate() @@ -116,6 +116,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // MARK: - Responding to App State Changes and System Events + func applicationWillEnterForeground(_ application: UIApplication) { + self.notificationsRegistrationService.renewDeviceToken() + } + func applicationDidBecomeActive(_ application: UIApplication) { NotificationsBadgesManager.shared.set(number: application.applicationIconBadgeNumber) AppsFlyerTracker.shared().trackAppLaunch() @@ -138,14 +142,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data ) { - NotificationRegistrator.shared.getGCMRegistrationToken(deviceToken: deviceToken) + self.notificationsRegistrationService.handleDeviceToken(deviceToken) } func application( _ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error ) { - print("error while registering to remote notifications: \(error)") + self.notificationsRegistrationService.handleRegistrationError(error) } func application( @@ -160,6 +164,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { NotificationsService().handleLocalNotification(with: notification.userInfo) } + func application( + _ application: UIApplication, + didRegister notificationSettings: UIUserNotificationSettings + ) { + self.notificationsRegistrationService.handleRegisteredNotificationSettings(notificationSettings) + } + // MARK: Private Helpers @objc @@ -168,11 +179,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return } - InstanceID.instanceID().instanceID { (result, error) in + InstanceID.instanceID().instanceID { [weak self] (result, error) in if let error = error { - print("Error fetching Firebase remote instanse ID: \(error)") + print("Error fetching Firebase remote instance ID: \(error)") } else if let result = result { - NotificationRegistrator.shared.registerDevice(result.token) + self?.notificationsRegistrationService.registerDevice(result.token) } } } diff --git a/Stepic/AuthAfterOnboardingSplitTest.swift b/Stepic/AuthAfterOnboardingSplitTest.swift deleted file mode 100644 index 20b994e927..0000000000 --- a/Stepic/AuthAfterOnboardingSplitTest.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// AuthAfterOnboardingSplitTest.swift -// Stepic -// -// Created by Ostrenkiy on 16/10/2018. -// Copyright © 2018 Alex Karpov. All rights reserved. -// - -import Foundation -import UIKit - -final class AuthAfterOnboardingSplitTest: SplitTestProtocol { - typealias GroupType = Group - - static var identifier = "auth_after_onboarding" - static var minParticipatingStartVersion = "1.70" - - var currentGroup: AuthAfterOnboardingSplitTest.Group - var analytics: ABAnalyticsServiceProtocol - - init(currentGroup: AuthAfterOnboardingSplitTest.Group, analytics: ABAnalyticsServiceProtocol) { - self.currentGroup = currentGroup - self.analytics = analytics - } - - enum Group: String, SplitTestGroupProtocol { - case control = "control" - case test = "test" - - static var groups: [AuthAfterOnboardingSplitTest.Group] = [.control, .test] - - var shouldShowAuth: Bool { - switch self { - case .control: - return true - case .test: - return false - } - } - } -} diff --git a/Stepic/AuthInfo.swift b/Stepic/AuthInfo.swift index a5d727378b..1e9dd6d731 100644 --- a/Stepic/AuthInfo.swift +++ b/Stepic/AuthInfo.swift @@ -84,7 +84,7 @@ class AuthInfo: NSObject { performLogoutActions() #else //Unregister from notifications - NotificationRegistrator.shared.unregisterFromNotifications(completion: { + NotificationsRegistrationService().unregisterFromNotifications(completion: { performLogoutActions() }) #endif diff --git a/Stepic/AuthNavigationViewController.swift b/Stepic/AuthNavigationViewController.swift index c4e7c1b5c1..9fa76b45a2 100644 --- a/Stepic/AuthNavigationViewController.swift +++ b/Stepic/AuthNavigationViewController.swift @@ -8,10 +8,10 @@ import UIKit -class AuthNavigationViewController: UINavigationController { - - var streaksAlertPresentationManager: StreaksAlertPresentationManager = StreaksAlertPresentationManager(source: .login) - var streaksNotificationSuggestionManager: NotificationSuggestionManager = NotificationSuggestionManager() +final class AuthNavigationViewController: UINavigationController { + private let streaksAlertPresentationManager = StreaksAlertPresentationManager(source: .login) + private let notificationSuggestionManager = NotificationSuggestionManager() + private let userActivitiesAPI = UserActivitiesAPI() enum Controller { case social @@ -40,12 +40,10 @@ class AuthNavigationViewController: UINavigationController { guard let userId = AuthInfo.shared.userId else { return } - let userActivitiesAPI = UserActivitiesAPI() - checkToken().then { - userActivitiesAPI.retrieve(user: userId) - }.done { userActivity in - if userActivity.didSolveThisWeek && self.streaksNotificationSuggestionManager.canShowAlert(context: .streak, after: .login) { - self.streaksNotificationSuggestionManager.didShowAlert(context: .streak) + + self.userActivitiesAPI.retrieve(user: userId).done { userActivity in + if userActivity.didSolveThisWeek + && self.notificationSuggestionManager.canShowAlert(context: .streak, after: .login) { self.streaksAlertPresentationManager.suggestStreak(streak: userActivity.currentStreak) } }.catch { error in diff --git a/Stepic/BaseCardsStepsViewController.swift b/Stepic/BaseCardsStepsViewController.swift index b876c4d2b4..60497af982 100644 --- a/Stepic/BaseCardsStepsViewController.swift +++ b/Stepic/BaseCardsStepsViewController.swift @@ -57,7 +57,25 @@ class BaseCardsStepsViewController: CardsStepsViewController { progressBar.progress = 0 if presenter == nil { - presenter = BaseCardsStepsPresenter(stepsAPI: StepsAPI(), lessonsAPI: LessonsAPI(), recommendationsAPI: RecommendationsAPI(), unitsAPI: UnitsAPI(), viewsAPI: ViewsAPI(), ratingsAPI: AdaptiveRatingsAPI(), ratingManager: AdaptiveRatingManager(courseId: course.id), statsManager: AdaptiveStatsManager(courseId: course.id), storageManager: AdaptiveStorageManager(), lastViewedUpdater: LocalProgressLastViewedUpdater(), notificationSuggestionManager: NotificationSuggestionManager(), notificationPermissionManager: NotificationPermissionManager(), course: course, view: self) + presenter = BaseCardsStepsPresenter( + stepsAPI: StepsAPI(), + lessonsAPI: LessonsAPI(), + recommendationsAPI: RecommendationsAPI(), + unitsAPI: UnitsAPI(), + viewsAPI: ViewsAPI(), + ratingsAPI: AdaptiveRatingsAPI(), + ratingManager: AdaptiveRatingManager(courseId: course.id), + statsManager: AdaptiveStatsManager(courseId: course.id), + storageManager: AdaptiveStorageManager(), + lastViewedUpdater: LocalProgressLastViewedUpdater(), + notificationSuggestionManager: NotificationSuggestionManager(), + notificationsRegistrationService: NotificationsRegistrationService( + presenter: NotificationsRequestAlertPresenter(context: .courseSubscription), + analytics: .init(source: .courseSubscription) + ), + course: course, + view: self + ) presenter?.refresh() } } diff --git a/Stepic/CardsStepsPresenter.swift b/Stepic/CardsStepsPresenter.swift index 5ce9e88621..8b02be1857 100644 --- a/Stepic/CardsStepsPresenter.swift +++ b/Stepic/CardsStepsPresenter.swift @@ -27,7 +27,6 @@ protocol CardsStepsView: class { func presentDiscussions(stepId: Int, discussionProxyId: String) func presentShareDialog(for link: String) func refreshCards() - func present(alertManager: AlertManager, alert: UIViewController) func updateProgress(rating: Int, prevMaxRating: Int, maxRating: Int, level: Int) func showCongratulation(for rating: Int, isSpecial: Bool, completion: (() -> Void)?) @@ -71,7 +70,7 @@ class BaseCardsStepsPresenter: CardsStepsPresenter, StepCardViewDelegate { internal var ratingsAPI: AdaptiveRatingsAPI internal var lastViewedUpdater: LocalProgressLastViewedUpdater internal var notificationSuggestionManager: NotificationSuggestionManager - internal var notificationPermissionManager: NotificationPermissionManager + internal var notificationsRegistrationService: NotificationsRegistrationServiceProtocol // FIXME: incapsulate/remove this var state: CardsStepsPresenterState = .loaded @@ -119,7 +118,7 @@ class BaseCardsStepsPresenter: CardsStepsPresenter, StepCardViewDelegate { return true } - init(stepsAPI: StepsAPI, lessonsAPI: LessonsAPI, recommendationsAPI: RecommendationsAPI, unitsAPI: UnitsAPI, viewsAPI: ViewsAPI, ratingsAPI: AdaptiveRatingsAPI, ratingManager: AdaptiveRatingManager, statsManager: AdaptiveStatsManager, storageManager: AdaptiveStorageManager, lastViewedUpdater: LocalProgressLastViewedUpdater, notificationSuggestionManager: NotificationSuggestionManager, notificationPermissionManager: NotificationPermissionManager, course: Course?, view: CardsStepsView) { + init(stepsAPI: StepsAPI, lessonsAPI: LessonsAPI, recommendationsAPI: RecommendationsAPI, unitsAPI: UnitsAPI, viewsAPI: ViewsAPI, ratingsAPI: AdaptiveRatingsAPI, ratingManager: AdaptiveRatingManager, statsManager: AdaptiveStatsManager, storageManager: AdaptiveStorageManager, lastViewedUpdater: LocalProgressLastViewedUpdater, notificationSuggestionManager: NotificationSuggestionManager, notificationsRegistrationService: NotificationsRegistrationServiceProtocol, course: Course?, view: CardsStepsView) { self.stepsAPI = stepsAPI self.lessonsAPI = lessonsAPI self.recommendationsAPI = recommendationsAPI @@ -131,10 +130,12 @@ class BaseCardsStepsPresenter: CardsStepsPresenter, StepCardViewDelegate { self.storageManager = storageManager self.lastViewedUpdater = lastViewedUpdater self.notificationSuggestionManager = notificationSuggestionManager - self.notificationPermissionManager = notificationPermissionManager + self.notificationsRegistrationService = notificationsRegistrationService self.course = course self.view = view + + self.notificationsRegistrationService.delegate = self } func refresh() { @@ -151,20 +152,7 @@ class BaseCardsStepsPresenter: CardsStepsPresenter, StepCardViewDelegate { } func appearedAfterSubscription() { - if #available(iOS 10.0, *) { - if notificationSuggestionManager.canShowAlert(context: .courseSubscription) { - notificationPermissionManager.getCurrentPermissionStatus().done { [weak self] status in - switch status { - case .notDetermined: - let alert = Alerts.notificationRequest.construct(context: .courseSubscription) - self?.view?.present(alertManager: Alerts.notificationRequest, alert: alert) - self?.notificationSuggestionManager.didShowAlert(context: .courseSubscription) - default: - break - } - } - } - } + self.notificationsRegistrationService.registerForRemoteNotifications() } private func refreshTopCardForOnboarding(stepIndex: Int) { @@ -515,6 +503,26 @@ class BaseCardsStepsPresenter: CardsStepsPresenter, StepCardViewDelegate { } } +// MARK: - BaseCardsStepsPresenter: NotificationsRegistrationServiceDelegate - + +extension BaseCardsStepsPresenter: NotificationsRegistrationServiceDelegate { + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + shouldPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) -> Bool { + return self.notificationSuggestionManager.canShowAlert(context: .courseSubscription) + } + + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + didPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) { + if alertType == .permission { + self.notificationSuggestionManager.didShowAlert(context: .courseSubscription) + } + } +} + enum CardsStepsError: Error { case noStepsInLesson case recommendationsNotLoaded diff --git a/Stepic/CardsStepsViewController.swift b/Stepic/CardsStepsViewController.swift index 071dadafdf..9c9a32c4c8 100644 --- a/Stepic/CardsStepsViewController.swift +++ b/Stepic/CardsStepsViewController.swift @@ -134,10 +134,6 @@ class CardsStepsViewController: UIViewController, CardsStepsView, ControllerWith state = .congratulation Alerts.congratulation.present(alert: controller, inController: ControllerHelper.getTopViewController() ?? self) } - - func present(alertManager: AlertManager, alert: UIViewController) { - alertManager.present(alert: alert, inController: self) - } } extension CardsStepsViewController: KolodaViewDelegate { diff --git a/Stepic/CertificatesViewController.swift b/Stepic/CertificatesViewController.swift index 6b6b0f4796..40b567bd3a 100644 --- a/Stepic/CertificatesViewController.swift +++ b/Stepic/CertificatesViewController.swift @@ -38,7 +38,7 @@ class CertificatesViewController: UIViewController, CertificatesView, Controller RoutingManager.auth.routeFrom(controller: strongSelf, success: nil, cancel: nil) }), for: .anonymous) registerPlaceholder(placeholder: StepikPlaceholder(.noConnection, action: { [weak self] in - self?.presenter?.checkStatus() + self?.presenter?.refreshCertificates() }), for: .connectionError) title = NSLocalizedString("Certificates", comment: "") diff --git a/Stepic/CommonNotificationsRequestAlertDataSource.swift b/Stepic/CommonNotificationsRequestAlertDataSource.swift new file mode 100644 index 0000000000..38d2637368 --- /dev/null +++ b/Stepic/CommonNotificationsRequestAlertDataSource.swift @@ -0,0 +1,54 @@ +// +// CommonNotificationsRequestAlertDataSource.swift +// Stepic +// +// Created by Ivan Magda on 29/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +final class CommonNotificationsRequestAlertDataSource: NotificationsRequestAlertDataSource { + var positiveAction: (() -> Void)? + var negativeAction: (() -> Void)? + + func alert( + for alertType: NotificationsRegistrationServiceAlertType, + in context: NotificationRequestAlertContext + ) -> UIViewController { + switch alertType { + case .permission: + let alert = NotificationRequestAlertViewController(context: context) + alert.yesAction = self.positiveAction + alert.noAction = self.negativeAction + + return alert + case .settings: + let alert = UIAlertController( + title: NSLocalizedString("DeniedNotificationsDefaultAlertTitle", comment: ""), + message: NSLocalizedString("DeniedNotificationsDefaultAlertMessage", comment: ""), + preferredStyle: .alert + ) + alert.addAction( + UIAlertAction( + title: NSLocalizedString("Settings", comment: ""), + style: .default, + handler: { _ in + self.positiveAction?() + } + ) + ) + alert.addAction( + UIAlertAction( + title: NSLocalizedString("No", comment: ""), + style: .cancel, + handler: { _ in + self.negativeAction?() + } + ) + ) + + return alert + } + } +} diff --git a/Stepic/CoursePreviewViewController.swift b/Stepic/CoursePreviewViewController.swift index 2c620dfc5a..08fde6955a 100644 --- a/Stepic/CoursePreviewViewController.swift +++ b/Stepic/CoursePreviewViewController.swift @@ -384,37 +384,44 @@ class CoursePreviewViewController: UIViewController, ShareableController { } //TODO : Add statuses - if let c = course { + guard let course = self.course else { + return + } - if !c.enrolled { - SVProgressHUD.show() - sender.isEnabled = false + if !course.enrolled { + SVProgressHUD.show() + sender.isEnabled = false - subscriber.join(course: c, source: .preview).done { [weak self] course in - SVProgressHUD.showSuccess(withStatus: "") - sender.isEnabled = true - sender.setTitle(NSLocalizedString("Continue", comment: ""), for: .normal) - self?.course = course - self?.initBarButtonItems(dropAvailable: course.enrolled) + self.subscriber.join(course: course, source: .preview).done { [weak self] course in + SVProgressHUD.showSuccess(withStatus: "") + sender.isEnabled = true + sender.setTitle(NSLocalizedString("Continue", comment: ""), for: .normal) + self?.course = course + self?.initBarButtonItems(dropAvailable: course.enrolled) - if let navigation = self?.navigationController { - LastStepRouter.continueLearning(for: course, using: navigation) - } - }.catch { _ in - SVProgressHUD.showError(withStatus: "") - sender.isEnabled = true + if let navigation = self?.navigationController { + LastStepRouter.continueLearning(for: course, didJustSubscribe: true, using: navigation) } - } else { - if AdaptiveStorageManager.shared.canOpenInAdaptiveMode(courseId: c.id) { - guard let cardsViewController = ControllerHelper.instantiateViewController(identifier: "CardsSteps", storyboardName: "Adaptive") as? BaseCardsStepsViewController else { - return - } - cardsViewController.hidesBottomBarWhenPushed = true - cardsViewController.course = c - self.navigationController?.pushViewController(cardsViewController, animated: true) - } else { - self.performSegue(withIdentifier: "showSections", sender: nil) + }.catch { _ in + SVProgressHUD.showError(withStatus: "") + sender.isEnabled = true + } + } else { + if AdaptiveStorageManager.shared.canOpenInAdaptiveMode(courseId: course.id) { + guard let cardsViewController = ControllerHelper.instantiateViewController( + identifier: "CardsSteps", + storyboardName: "Adaptive" + ) as? BaseCardsStepsViewController else { + return } + + cardsViewController.hidesBottomBarWhenPushed = true + cardsViewController.course = course + cardsViewController.didJustSubscribe = false + + self.navigationController?.pushViewController(cardsViewController, animated: true) + } else { + self.performSegue(withIdentifier: "showSections", sender: nil) } } } diff --git a/Stepic/CoursesAPI.swift b/Stepic/CoursesAPI.swift index 57b201bf2a..c83977dea3 100644 --- a/Stepic/CoursesAPI.swift +++ b/Stepic/CoursesAPI.swift @@ -15,15 +15,23 @@ class CoursesAPI: APIEndpoint { override var name: String { return "courses" } @discardableResult func retrieve(ids: [Int], headers: [String: String] = AuthInfo.shared.initialHTTPHeaders, existing: [Course]) -> Promise<[Course]> { + if ids.isEmpty { + return .value([]) + } + return getObjectsByIds(ids: ids, updating: existing) } @available(*, deprecated, message: "Legacy: we want to pass existing") @discardableResult func retrieve(ids: [Int], headers: [String: String] = AuthInfo.shared.initialHTTPHeaders) -> Promise<[Course]> { + if ids.isEmpty { + return .value([]) + } + return getObjectsByIds(ids: ids, updating: Course.getCourses(ids)) } - func retrieve(tag: Int? = nil, featured: Bool? = nil, enrolled: Bool? = nil, excludeEnded: Bool? = nil, isPublic: Bool? = nil, order: String? = nil, language: String? = nil, page: Int = 1) -> Promise<([Course], Meta)> { + func retrieve(tag: Int? = nil, featured: Bool? = nil, enrolled: Bool? = nil, excludeEnded: Bool? = nil, isPublic: Bool? = nil, isPopular: Bool? = nil, order: String? = nil, language: String? = nil, page: Int = 1) -> Promise<([Course], Meta)> { var params = Parameters() if let isFeatured = featured { @@ -42,6 +50,10 @@ class CoursesAPI: APIEndpoint { params["is_public"] = isPublic ? "true" : "false" } + if let isPopular = isPopular { + params["is_popular"] = isPopular ? "true" : "false" + } + if let order = order { params["order"] = order } diff --git a/Stepic/CustomSearchBar.swift b/Stepic/CustomSearchBar.swift deleted file mode 100644 index 729109ba67..0000000000 --- a/Stepic/CustomSearchBar.swift +++ /dev/null @@ -1,179 +0,0 @@ -// -// CustomSearchBar.swift -// Stepic -// -// Created by Ostrenkiy on 13.09.17. -// Copyright © 2017 Alex Karpov. All rights reserved. -// - -import Foundation -import SnapKit - -protocol CustomSearchBarDelegate: class { - func changedText(in searchBar: CustomSearchBar, to text: String) - func startedEditing(in searchBar: CustomSearchBar) - func cancelPressed(in searchBar: CustomSearchBar) - func returnPressed(in searchBar: CustomSearchBar) -} - -@IBDesignable -class CustomSearchBar: NibInitializableView, UITextFieldDelegate { - - weak var delegate: CustomSearchBarDelegate? - @IBOutlet weak var cancelButton: UIButton! - @IBOutlet weak var textField: UITextField! - - @IBOutlet weak var textFieldCancelHorizontalSpaceConstraint: NSLayoutConstraint! - @IBOutlet weak var cancelButtonWidthConstraint: NSLayoutConstraint! - - let cancelWidth: CGFloat = 70 - let textFieldCancelDistance: CGFloat = 8 - - private var isCancelActive: Bool = false - - override var nibName: String { - return "CustomSearchBar" - } - - @IBInspectable - var barTintColor: UIColor? = UIColor.white { - didSet { - backgroundColor = barTintColor - } - } - - @IBInspectable - var text: String { - set(newText) { - textField.text = newText - } - get { - return textField.text ?? "" - } - } - - @IBInspectable - var mainColor: UIColor? = UIColor.blue { - didSet { - cancelButton.setTitleColor(mainColor, for: .normal) - textField.tintColor = mainColor - } - } - - @IBInspectable - var placeholder: String = NSLocalizedString("Search", comment: "") { - didSet { - textField.placeholder = placeholder - } - } - - @IBInspectable - var hasShadowImage: Bool = true { - didSet { - shadowView.isHidden = !hasShadowImage - } - } - - private lazy var shadowView: UIView = { - let v = UIView() - self.view.addSubview(v) - v.backgroundColor = UIColor.lightGray - v.snp.makeConstraints { make -> Void in - make.leading.trailing.equalTo(self.view) - make.bottom.equalTo(self.view) - make.height.equalTo(0.5) - } - return v - }() - - private lazy var glassView: UIView = { - let v = UIView() - let imageSize: Int = 16 - let horizontalInset: Int = 8 - v.snp.makeConstraints { make -> Void in - make.width.equalTo(imageSize + 2 * horizontalInset) - make.height.equalTo(imageSize) - } - - let glassImage = UIImageView(image: #imageLiteral(resourceName: "search_glass")) - glassImage.contentMode = .scaleAspectFit - v.addSubview(glassImage) - glassImage.snp.makeConstraints { make -> Void in - make.width.height.equalTo(imageSize) - make.top.bottom.equalTo(v) - make.leading.equalTo(v).offset(horizontalInset) - make.trailing.equalTo(v).offset(-horizontalInset) - } - return v - }() - - override func setupSubviews() { - textField.placeholder = placeholder - backgroundColor = barTintColor - cancelButton.setTitleColor(mainColor, for: .normal) - cancelButton.setTitle(NSLocalizedString("Cancel", comment: ""), for: .normal) - textField.text = text - hasShadowImage = true - - textField.leftViewMode = .always - textField.leftView = glassView - textField.layoutSubviews() - textField.delegate = self - - cancelButton.addTarget(self, action: #selector(CustomSearchBar.cancel), for: UIControlEvents.touchUpInside) - textField.addTarget(self, action: #selector(CustomSearchBar.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged) - } - - @objc func cancel() { - if isCancelActive { - setCancelButton(visible: false, animated: true) - } - textField.resignFirstResponder() - delegate?.cancelPressed(in: self) - textField.text = "" - } - - @objc func textFieldDidChange(textField: UITextField) { - guard let text = textField.text else { - return - } - delegate?.changedText(in: self, to: text) - } - - private func setCancelButton(visible: Bool, animated: Bool) { - if visible { - textFieldCancelHorizontalSpaceConstraint.constant = textFieldCancelDistance - cancelButtonWidthConstraint.constant = cancelWidth - } else { - cancelButtonWidthConstraint.constant = 0 - textFieldCancelHorizontalSpaceConstraint.constant = 0 - } - - isCancelActive = visible - - if animated { - UIView.animate(withDuration: 0.3, animations: { - [weak self] in - self?.view.updateConstraints() - self?.view.layoutSubviews() - }) - } else { - self.view.updateConstraints() - self.view.layoutSubviews() - } - } - - // MARK: UITextFieldDelegate - func textFieldDidBeginEditing(_ textField: UITextField) { - if !isCancelActive { - setCancelButton(visible: true, animated: true) - } - delegate?.startedEditing(in: self) - } - - func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.resignFirstResponder() - delegate?.returnPressed(in: self) - return true - } -} diff --git a/Stepic/CustomSearchBar.xib b/Stepic/CustomSearchBar.xib deleted file mode 100644 index 42423522c5..0000000000 --- a/Stepic/CustomSearchBar.xib +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Stepic/EmailAuthPresenter.swift b/Stepic/EmailAuthPresenter.swift index 07ce3a9bf0..ea006a393b 100644 --- a/Stepic/EmailAuthPresenter.swift +++ b/Stepic/EmailAuthPresenter.swift @@ -45,7 +45,7 @@ class EmailAuthPresenter { AuthInfo.shared.token = token AuthInfo.shared.authorizationType = authorizationType - NotificationRegistrator.shared.registerForRemoteNotificationsIfAlreadyAsked() + NotificationsRegistrationService().renewDeviceToken() return self.stepicsAPI.retrieveCurrentUser() }.then { user -> Promise in diff --git a/Stepic/GeneralInfoTableViewCell.swift b/Stepic/GeneralInfoTableViewCell.swift index 7b95578c6e..620055cbba 100644 --- a/Stepic/GeneralInfoTableViewCell.swift +++ b/Stepic/GeneralInfoTableViewCell.swift @@ -19,6 +19,8 @@ class GeneralInfoTableViewCell: UITableViewCell { @IBOutlet weak var joinButton: UIButton! + private let splitTestingService = SplitTestingService(analyticsService: AnalyticsUserProperties(), storage: UserDefaults.standard) + class func heightForCellWith(_ course: Course) -> CGFloat { let constrainHeight: CGFloat = 108 let width = UIScreen.main.bounds.width - 16 @@ -56,7 +58,13 @@ class GeneralInfoTableViewCell: UITableViewCell { joinButton.setTitle(NSLocalizedString("Continue", comment: ""), for: .normal) } else { joinButton.setStepicGreenStyle() - joinButton.setTitle(Constants.joinCourseButtonText, for: .normal) + + var joinTitle = NSLocalizedString("WidgetButtonJoin", comment: "") + if JoinCourseStringSplitTest.shouldParticipate { + let joinSplitTest = splitTestingService.fetchSplitTest(JoinCourseStringSplitTest.self) + joinTitle = joinSplitTest.currentGroup.joinText + } + joinButton.setTitle(joinTitle, for: .normal) } } diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Contents.json b/Stepic/Images.xcassets/AppIcon.appiconset/Contents.json index 191d433565..ca06608b27 100644 --- a/Stepic/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Stepic/Images.xcassets/AppIcon.appiconset/Contents.json @@ -3,176 +3,152 @@ { "size" : "20x20", "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", + "filename" : "Icon-20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", + "filename" : "Icon-20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", + "filename" : "Icon-29.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", + "filename" : "Icon-29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", + "filename" : "Icon-29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", + "filename" : "Icon-40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", + "filename" : "Icon-40@3x.png", "scale" : "3x" }, { "size" : "57x57", "idiom" : "iphone", - "filename" : "Icon-App-57x57@1x.png", + "filename" : "Icon-57.png", "scale" : "1x" }, { "size" : "57x57", "idiom" : "iphone", - "filename" : "Icon-App-57x57@2x.png", + "filename" : "Icon-114.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", + "filename" : "Icon-120.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", + "filename" : "Icon-180.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", + "filename" : "Icon-20.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", + "filename" : "Icon-40.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", + "filename" : "Icon-30.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", + "filename" : "Icon-58.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", + "filename" : "Icon-41.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", + "filename" : "Icon-80.png", "scale" : "2x" }, { "size" : "50x50", "idiom" : "ipad", - "filename" : "Icon-Small-50x50@1x.png", + "filename" : "Icon-50.png", "scale" : "1x" }, { "size" : "50x50", "idiom" : "ipad", - "filename" : "Icon-Small-50x50@2x.png", + "filename" : "Icon-100.png", "scale" : "2x" }, { "size" : "72x72", "idiom" : "ipad", - "filename" : "Icon-App-72x72@1x.png", + "filename" : "Icon-72.png", "scale" : "1x" }, { "size" : "72x72", "idiom" : "ipad", - "filename" : "Icon-App-72x72@2x.png", + "filename" : "Icon-144.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", + "filename" : "Icon-76.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", + "filename" : "Icon-152.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", + "filename" : "Icon-83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", - "filename" : "ItunesArtwork@2x.png", + "filename" : "icon-b-black.png", "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "iphone", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@3x.png", - "scale" : "3x" } ], "info" : { diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-100.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-100.png new file mode 100644 index 0000000000..de8f91f332 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-100.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-114.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-114.png new file mode 100644 index 0000000000..75e768ddb4 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-114.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-120.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-120.png new file mode 100644 index 0000000000..0575bbb89b Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-120.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-144.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-144.png new file mode 100644 index 0000000000..ea38543a1b Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-144.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-152.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-152.png new file mode 100644 index 0000000000..6436269837 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-152.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-180.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-180.png new file mode 100644 index 0000000000..27fa3853f1 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-180.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20.png new file mode 100644 index 0000000000..e958837386 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20@2x.png new file mode 100644 index 0000000000..65fa313248 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20@2x.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20@3x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20@3x.png new file mode 100644 index 0000000000..7078d0dfc2 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-20@3x.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29.png new file mode 100644 index 0000000000..692c1b0dae Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png new file mode 100644 index 0000000000..a6408ba2c3 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29@2x.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png new file mode 100644 index 0000000000..7ab7c1a770 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-29@3x.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-30.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-30.png new file mode 100644 index 0000000000..692c1b0dae Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-30.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40.png new file mode 100644 index 0000000000..c4f3f3889b Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png new file mode 100644 index 0000000000..55e1eb2ac3 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png new file mode 100644 index 0000000000..b04b107a16 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-41.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-41.png new file mode 100644 index 0000000000..c4f3f3889b Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-41.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-50.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-50.png new file mode 100644 index 0000000000..3769d32e5d Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-50.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-57.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-57.png new file mode 100644 index 0000000000..d2bd602342 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-57.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-58.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-58.png new file mode 100644 index 0000000000..5d431e4e7f Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-58.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-72.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-72.png new file mode 100644 index 0000000000..8a9e2c161e Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-72.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-76.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-76.png new file mode 100644 index 0000000000..8823ef23f5 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-76.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-80.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-80.png new file mode 100644 index 0000000000..cbce7fd409 Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-80.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png new file mode 100644 index 0000000000..1886c91fad Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index c069226494..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 7dce318d8f..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 069d23a1c9..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 832b346022..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index fe66ca1692..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index 5ea3a098c6..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 7dce318d8f..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index d433172a71..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 2e097b68bf..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png deleted file mode 100644 index a63f03413d..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png deleted file mode 100644 index f795a118ef..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png deleted file mode 100644 index 069d23a1c9..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 2e097b68bf..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index f8e0c65932..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png deleted file mode 100644 index 478b87bcf8..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png deleted file mode 100644 index 9d62bd1aad..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index d99dbeebd4..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index a96d5e5a0f..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png deleted file mode 100644 index b084e47c2a..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@3x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 888a2b0830..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png deleted file mode 100644 index 77c1a2f32d..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png deleted file mode 100644 index 2bcf5a637c..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/Stepic/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png deleted file mode 100644 index b7c3fa2122..0000000000 Binary files a/Stepic/Images.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png and /dev/null differ diff --git a/Stepic/Images.xcassets/AppIcon.appiconset/icon-b-black.png b/Stepic/Images.xcassets/AppIcon.appiconset/icon-b-black.png new file mode 100644 index 0000000000..ece1fcdbee Binary files /dev/null and b/Stepic/Images.xcassets/AppIcon.appiconset/icon-b-black.png differ diff --git a/Stepic/Info.plist b/Stepic/Info.plist index 81a2b82593..5b354e9714 100644 --- a/Stepic/Info.plist +++ b/Stepic/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.70 + 1.71 CFBundleSignature ???? CFBundleURLTypes @@ -52,7 +52,7 @@ CFBundleVersion - 107 + 109 Fabric APIKey diff --git a/Stepic/JoinCourseStringSplitTest.swift b/Stepic/JoinCourseStringSplitTest.swift new file mode 100644 index 0000000000..9c7e9ad4e6 --- /dev/null +++ b/Stepic/JoinCourseStringSplitTest.swift @@ -0,0 +1,41 @@ +// +// JoinCourseStringSplitTest.swift +// Stepic +// +// Created by Ostrenkiy on 26/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import UIKit + +final class JoinCourseStringSplitTest: SplitTestProtocol { + typealias GroupType = Group + + static let identifier = "join_course_string" + static let minParticipatingStartVersion = "1.71" + + var currentGroup: Group + var analytics: ABAnalyticsServiceProtocol + + init(currentGroup: Group, analytics: ABAnalyticsServiceProtocol) { + self.currentGroup = currentGroup + self.analytics = analytics + } + + enum Group: String, SplitTestGroupProtocol { + case control = "control" + case test = "test" + + static var groups: [Group] = [.control, .test] + + var joinText: String { + switch self { + case .control: + return NSLocalizedString("WidgetButtonJoin", comment: "") + case .test: + return NSLocalizedString("WidgetButtonJoinTest", comment: "") + } + } + } +} diff --git a/Stepic/LastStepRouter.swift b/Stepic/LastStepRouter.swift index 99bfb3f3d0..870c1b0c7a 100644 --- a/Stepic/LastStepRouter.swift +++ b/Stepic/LastStepRouter.swift @@ -19,6 +19,7 @@ class LastStepRouter { static func continueLearning( for course: Course, isAdaptive: Bool? = nil, + didJustSubscribe: Bool = false, using navigationController: UINavigationController ) { guard let lastStepId = course.lastStepId else { @@ -34,7 +35,7 @@ class LastStepRouter { course.lastStep = newLastStep CoreDataHelper.instance.save() }.ensure { - navigate(for: course, isAdaptive: isAdaptive, using: navigationController) + self.navigate(for: course, isAdaptive: isAdaptive, didJustSubscribe: didJustSubscribe, using: navigationController) }.catch { _ in print("error while updating lastStep") @@ -43,16 +44,18 @@ class LastStepRouter { private static func navigate( for course: Course, - isAdaptive: Bool? = nil, + isAdaptive: Bool?, + didJustSubscribe: Bool, using navigationController: UINavigationController ) { - let shouldOpenInAdaptiveOMode = isAdaptive ?? AdaptiveStorageManager.shared.canOpenInAdaptiveMode(courseId: course.id) - if shouldOpenInAdaptiveOMode { + let shouldOpenInAdaptiveMode = isAdaptive ?? AdaptiveStorageManager.shared.canOpenInAdaptiveMode(courseId: course.id) + if shouldOpenInAdaptiveMode { guard let cardsViewController = ControllerHelper.instantiateViewController(identifier: "CardsSteps", storyboardName: "Adaptive") as? BaseCardsStepsViewController else { return } cardsViewController.hidesBottomBarWhenPushed = true cardsViewController.course = course + cardsViewController.didJustSubscribe = didJustSubscribe navigationController.pushViewController(cardsViewController, animated: true) SVProgressHUD.showSuccess(withStatus: "") return diff --git a/Stepic/Modules/ContinueCourse/ContinueCourseViewController.swift b/Stepic/Modules/ContinueCourse/ContinueCourseViewController.swift index 08e25f106f..81896f0536 100644 --- a/Stepic/Modules/ContinueCourse/ContinueCourseViewController.swift +++ b/Stepic/Modules/ContinueCourse/ContinueCourseViewController.swift @@ -82,6 +82,8 @@ extension ContinueCourseViewController: ContinueCourseViewControllerProtocol { if viewModel.shouldShowTooltip { // Cause anchor should be in true position DispatchQueue.main.async { [weak self] in + self?.continueCourseView?.setNeedsLayout() + self?.continueCourseView?.layoutIfNeeded() self?.continueLearningTooltip.show( direction: .up, in: continueCourseView, diff --git a/Stepic/Modules/CourseList/Provider/CourseListNetworkService.swift b/Stepic/Modules/CourseList/Provider/CourseListNetworkService.swift index 747f241ff3..a073eba302 100644 --- a/Stepic/Modules/CourseList/Provider/CourseListNetworkService.swift +++ b/Stepic/Modules/CourseList/Provider/CourseListNetworkService.swift @@ -79,6 +79,7 @@ final class PopularCourseListNetworkService: BaseCourseListNetworkService, self.coursesAPI.retrieve( excludeEnded: true, isPublic: true, + isPopular: true, order: "-activity", language: self.type.language.popularCoursesParameter, page: page diff --git a/Stepic/Modules/CourseList/Utils/ButtonDescriptionFactory.swift b/Stepic/Modules/CourseList/Utils/ButtonDescriptionFactory.swift index 339eb03005..e93c1c9387 100644 --- a/Stepic/Modules/CourseList/Utils/ButtonDescriptionFactory.swift +++ b/Stepic/Modules/CourseList/Utils/ButtonDescriptionFactory.swift @@ -12,10 +12,18 @@ struct ButtonDescriptionFactory { let course: Course let isAuthorized: Bool + private let splitTestingService = SplitTestingService(analyticsService: AnalyticsUserProperties(), storage: UserDefaults.standard) + func makePrimary() -> CourseWidgetViewModel.ButtonDescription { + var joinTitle = NSLocalizedString("WidgetButtonJoin", comment: "") + if JoinCourseStringSplitTest.shouldParticipate { + let joinSplitTest = splitTestingService.fetchSplitTest(JoinCourseStringSplitTest.self) + joinTitle = joinSplitTest.currentGroup.joinText + } + let title = self.course.enrolled && isAuthorized ? NSLocalizedString("WidgetButtonLearn", comment: "") - : NSLocalizedString("WidgetButtonJoin", comment: "") + : joinTitle return CourseWidgetViewModel.ButtonDescription( title: title, isCallToAction: !self.course.enrolled || !isAuthorized diff --git a/Stepic/NotificationPermissionManager.swift b/Stepic/NotificationPermissionStatus.swift similarity index 65% rename from Stepic/NotificationPermissionManager.swift rename to Stepic/NotificationPermissionStatus.swift index 264ac490ef..fd701a308b 100644 --- a/Stepic/NotificationPermissionManager.swift +++ b/Stepic/NotificationPermissionStatus.swift @@ -1,8 +1,8 @@ // -// NotificationPermissionManager.swift +// NotificationPermissionStatus.swift // Stepic // -// Created by Ostrenkiy on 28.02.2018. +// Created by Ivan Magda on 19/10/2018. // Copyright © 2018 Alex Karpov. All rights reserved. // @@ -28,29 +28,13 @@ enum NotificationPermissionStatus { } } - @available(iOS 10.0, *) - init(userNotificationAuthStatus: UNAuthorizationStatus) { - switch userNotificationAuthStatus { - case .authorized: - self = .authorized - case .denied: - self = .denied - case .notDetermined: - self = .notDetermined - } - } -} - -class NotificationPermissionManager { - func getCurrentPermissionStatus() -> Guarantee { + static var current: Guarantee { return Guarantee { seal in if #available(iOS 10.0, *) { - let current = UNUserNotificationCenter.current() - current.getNotificationSettings(completionHandler: { (settings) in - seal(NotificationPermissionStatus(userNotificationAuthStatus: settings.authorizationStatus)) - }) + UNUserNotificationCenter.current().getNotificationSettings { + seal(NotificationPermissionStatus(authorizationStatus: $0.authorizationStatus)) + } } else { - // Fallback on earlier versions, we can not determine if we denied push notifications or not if UIApplication.shared.isRegisteredForRemoteNotifications { seal(.authorized) } else { @@ -59,4 +43,16 @@ class NotificationPermissionManager { } } } + + @available(iOS 10.0, *) + init(authorizationStatus: UNAuthorizationStatus) { + switch authorizationStatus { + case .authorized: + self = .authorized + case .denied: + self = .denied + case .notDetermined: + self = .notDetermined + } + } } diff --git a/Stepic/NotificationRegistrator.swift b/Stepic/NotificationRegistrator.swift deleted file mode 100644 index e55a131cc6..0000000000 --- a/Stepic/NotificationRegistrator.swift +++ /dev/null @@ -1,159 +0,0 @@ -// -// NotificationRegistrator.swift -// Stepic -// -// Created by Alexander Karpov on 21.04.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import UIKit -import FirebaseMessaging -import FirebaseCore -import FirebaseInstanceID -import PromiseKit -import UserNotifications - -class NotificationRegistrator { - static let shared = NotificationRegistrator() - - let notificationPermissionManager = NotificationPermissionManager() - - private init () { } - - func registerForRemoteNotificationsIfAlreadyAsked() { - if #available(iOS 10.0, *) { - notificationPermissionManager.getCurrentPermissionStatus().done { [weak self] status in - switch status { - case .authorized: - self?.registerForRemoteNotifications() - default: - return - } - } - } else { - registerForRemoteNotifications() - } - } - - func registerForRemoteNotifications() { - return registerForRemoteNotifications(UIApplication.shared) - } - - func registerForRemoteNotifications(_ application: UIApplication) { - if StepicApplicationsInfo.shouldRegisterNotifications { - if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound], completionHandler: {_, _ in}) - } else { - let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) - application.registerUserNotificationSettings(settings) - } - application.registerForRemoteNotifications() - } - - if AuthInfo.shared.isAuthorized { - InstanceID.instanceID().instanceID { [weak self] (result, error) in - if let error = error { - print("Error fetching Firebase remote instanse ID: \(error)") - } else if let result = result { - self?.registerDevice(result.token) - } - } - } - } - - func getGCMRegistrationToken(deviceToken: Data) { - Messaging.messaging().apnsToken = deviceToken - } - - var registrationOptions = [String: AnyObject]() - - let registrationKey = "onRegistrationCompleted" - - func registerDevice(_ registrationToken: String, forceCreation: Bool = false) { - print("Registration Token: \(registrationToken)") - - let newDevice = Device(registrationId: registrationToken, deviceDescription: DeviceInfo.current.deviceInfoString) - - //TODO: Remove this after refactoring errors - checkToken().then { _ -> Promise in - if let savedDeviceId = DeviceDefaults.sharedDefaults.deviceId, !forceCreation { - print("notification registrator: retrieve device by saved deviceId = \(savedDeviceId)") - return ApiDataDownloader.devices.retrieve(deviceId: savedDeviceId) - } else { - return ApiDataDownloader.devices.create(newDevice) - } - }.then { remoteDevice -> Promise in - if remoteDevice.isBadgesEnabled { - return .value(remoteDevice) - } else { - remoteDevice.isBadgesEnabled = true - return ApiDataDownloader.devices.update(remoteDevice) - } - }.done { device -> Void in - print("notification registrator: device registered, info = \(device.json)") - DeviceDefaults.sharedDefaults.deviceId = device.id - }.catch { error in - switch error { - case DeviceError.notFound: - print("notification registrator: device not found, create new") - self.registerDevice(registrationToken, forceCreation: true) - case DeviceError.other(_, _, let message): - print("notification registrator: device registration error, error = \(String(describing: message))") - AnalyticsReporter.reportEvent(AnalyticsEvents.Errors.registerDevice, parameters: ["message": "\(String(describing: message))"]) - default: - print("notification registrator: device registration error, error = \(error)") - AnalyticsReporter.reportEvent(AnalyticsEvents.Errors.registerDevice, parameters: ["message": "\(error.localizedDescription)"]) - } - } - } - - func unregisterFromNotifications() -> Guarantee { - return Guarantee { seal in - UIApplication.shared.unregisterForRemoteNotifications() - - if let deviceId = DeviceDefaults.sharedDefaults.deviceId { - ApiDataDownloader.devices.delete(deviceId).done { () in - print("notification registrator: successfully delete device, id = \(deviceId)") - seal(()) - }.catch { error in - switch error { - case DeviceError.notFound: - print("notification registrator: device not found on deletion, id = \(deviceId)") - default: - if let userId = AuthInfo.shared.userId, - let token = AuthInfo.shared.token { - - let deleteTask = DeleteDeviceExecutableTask(userId: userId, deviceId: deviceId) - ExecutionQueues.sharedQueues.connectionAvailableExecutionQueue.push(deleteTask) - - let userPersistencyManager = PersistentUserTokenRecoveryManager(baseName: "Users") - userPersistencyManager.writeStepicToken(token, userId: userId) - - let taskPersistencyManager = PersistentTaskRecoveryManager(baseName: "Tasks") - taskPersistencyManager.writeTask(deleteTask, name: deleteTask.id) - - let queuePersistencyManager = PersistentQueueRecoveryManager(baseName: "Queues") - queuePersistencyManager.writeQueue(ExecutionQueues.sharedQueues.connectionAvailableExecutionQueue, key: ExecutionQueues.sharedQueues.connectionAvailableExecutionQueueKey) - } else { - print("notification registrator: could not get current user ID or token to delete device") - AnalyticsReporter.reportEvent(AnalyticsEvents.Errors.unregisterDeviceInvalidCredentials) - } - } - seal(()) - } - } else { - print("notification registrator: no saved device") - seal(()) - } - } - } -} - -extension NotificationRegistrator { - @available(*, deprecated, message: "Legacy method with callbacks") - func unregisterFromNotifications(completion: @escaping (() -> Void)) { - unregisterFromNotifications().done { - completion() - }.catch { _ in } - } -} diff --git a/Stepic/NotificationRequestAlertContext.swift b/Stepic/NotificationRequestAlertContext.swift index d64bd82831..981cbe04af 100644 --- a/Stepic/NotificationRequestAlertContext.swift +++ b/Stepic/NotificationRequestAlertContext.swift @@ -12,6 +12,7 @@ enum NotificationRequestAlertContext: String { case streak = "streak" case notificationsTab = "notifications_tab" case courseSubscription = "course_subscription" + case `default` var title: String { switch self { @@ -21,6 +22,8 @@ enum NotificationRequestAlertContext: String { return NSLocalizedString("NotificationTabNotificationRequestAlertTitle", comment: "") case .courseSubscription: return NSLocalizedString("CourseSubscriptionNotificationRequestAlertTitle", comment: "") + case .default: + return NSLocalizedString("NotificationRequestDefaultAlertTitle", comment: "") } } @@ -40,6 +43,8 @@ enum NotificationRequestAlertContext: String { return NSLocalizedString("NotificationTabNotificationRequestAlertMessage", comment: "") case .courseSubscription: return NSLocalizedString("CourseSubscriptionNotificationRequestAlertMessage", comment: "") + case .default: + return NSLocalizedString("NotificationRequestDefaultAlertMessage", comment: "") } } diff --git a/Stepic/NotificationRequestAlertManager.swift b/Stepic/NotificationRequestAlertManager.swift deleted file mode 100644 index 8414327440..0000000000 --- a/Stepic/NotificationRequestAlertManager.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// NotificationRequestAlertManager.swift -// Stepic -// -// Created by Ostrenkiy on 06.03.2018. -// Copyright © 2018 Alex Karpov. All rights reserved. -// - -import Foundation -import Presentr - -class NotificationRequestAlertManager: AlertManager { - func present(alert: UIViewController, inController controller: UIViewController) { - controller.customPresentViewController(presenter, viewController: alert, animated: true, completion: nil) - } - - let presenter: Presentr = { - let presenter = Presentr(presentationType: .dynamic(center: .center)) - presenter.roundCorners = true - return presenter - }() - - func construct(context: NotificationRequestAlertContext) -> NotificationRequestAlertViewController { - let alert = NotificationRequestAlertViewController(nibName: "NotificationRequestAlertViewController", bundle: nil) - alert.context = context - alert.yesAction = { - NotificationRegistrator.shared.registerForRemoteNotifications() - } - alert.noAction = {} - return alert - } -} diff --git a/Stepic/NotificationRequestAlertViewController.swift b/Stepic/NotificationRequestAlertViewController.swift index e58fc59ce0..410b55f8c2 100644 --- a/Stepic/NotificationRequestAlertViewController.swift +++ b/Stepic/NotificationRequestAlertViewController.swift @@ -10,8 +10,7 @@ import UIKit import Lottie import SnapKit -class NotificationRequestAlertViewController: UIViewController { - +final class NotificationRequestAlertViewController: UIViewController { @IBOutlet weak var imageContainerView: UIView! @IBOutlet weak var imageContainerViewHeight: NSLayoutConstraint! @@ -23,14 +22,23 @@ class NotificationRequestAlertViewController: UIViewController { var messageLabelWidth: Constraint? let animationView: LOTAnimationView = LOTAnimationView(name: "onboardingAnimation4") - var yesAction : (() -> Void)? - var noAction : (() -> Void)? + var yesAction: (() -> Void)? + var noAction: (() -> Void)? - var context: NotificationRequestAlertContext! + var context = NotificationRequestAlertContext.default //Streaks Context var currentStreak: Int = 0 + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } + + init(context: NotificationRequestAlertContext) { + self.context = context + super.init(nibName: nil, bundle: nil) + } + override func viewDidLoad() { super.viewDidLoad() @@ -66,10 +74,6 @@ class NotificationRequestAlertViewController: UIViewController { yesButton.setTitle(NSLocalizedString("Yes", comment: ""), for: .normal) } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - } - @IBAction func noPressed(_ sender: UIButton) { self.dismiss(animated: true, completion: nil) AnalyticsReporter.reportEvent(AnalyticsEvents.NotificationRequest.rejected(context: context)) diff --git a/Stepic/StreaksNotificationSuggestionManager.swift b/Stepic/NotificationSuggestionManager.swift similarity index 55% rename from Stepic/StreaksNotificationSuggestionManager.swift rename to Stepic/NotificationSuggestionManager.swift index 0d6f7bfbe2..42d8e4e81b 100644 --- a/Stepic/StreaksNotificationSuggestionManager.swift +++ b/Stepic/NotificationSuggestionManager.swift @@ -8,23 +8,25 @@ import Foundation -class NotificationSuggestionManager { +final class NotificationSuggestionManager { private let defaults = UserDefaults.standard - private let streakAlertShownCntKey = "streakAlertShownCntKey" - - private let lastStreakAlertShownTimeKey = "lastStreakAlertShownTimeKey" - private let lastNotificationsTabNotificationRequestShownTimeKey = "lastNotificationsTabNotificationRequestShownTimeKey" - private let lastCourseSubscriptionNotificationRequestShownTimeKey = "lastCourseSubscriptionNotificationRequestShownTimeKey" + private static let streakAlertShownCntKey = "streakAlertShownCntKey" + private static let lastStreakAlertShownTimeKey = "lastStreakAlertShownTimeKey" + private static let lastNotificationsTabNotificationRequestShownTimeKey = "lastNotificationsTabNotificationRequestShownTimeKey" + private static let lastCourseSubscriptionNotificationRequestShownTimeKey = "lastCourseSubscriptionNotificationRequestShownTimeKey" + private static let lastDefaultAlertShownTimeKey = "lastDefaultAlertShownTimeKey" func lastTimeKey(for context: NotificationRequestAlertContext) -> String { switch context { case .streak: - return lastStreakAlertShownTimeKey + return NotificationSuggestionManager.lastStreakAlertShownTimeKey case .courseSubscription: - return lastCourseSubscriptionNotificationRequestShownTimeKey + return NotificationSuggestionManager.lastCourseSubscriptionNotificationRequestShownTimeKey case .notificationsTab: - return lastNotificationsTabNotificationRequestShownTimeKey + return NotificationSuggestionManager.lastNotificationsTabNotificationRequestShownTimeKey + case .default: + return NotificationSuggestionManager.lastDefaultAlertShownTimeKey } } @@ -53,7 +55,7 @@ class NotificationSuggestionManager { var streakAlertShownCnt: Int { get { - if let cnt = defaults.value(forKey: streakAlertShownCntKey) as? Int { + if let cnt = defaults.value(forKey: NotificationSuggestionManager.streakAlertShownCntKey) as? Int { return cnt } else { self.streakAlertShownCnt = 0 @@ -62,7 +64,7 @@ class NotificationSuggestionManager { } set(value) { - defaults.set(value, forKey: streakAlertShownCntKey) + defaults.set(value, forKey: NotificationSuggestionManager.streakAlertShownCntKey) } } @@ -86,15 +88,22 @@ class NotificationSuggestionManager { guard let trigger = trigger else { return false } - let commonChecks = AuthInfo.shared.isAuthorized && isAlertAvailableNow(context: context) && PreferencesContainer.notifications.allowStreaksNotifications == false && StepicApplicationsInfo.streaksEnabled + + let commonChecks = AuthInfo.shared.isAuthorized + && self.isAlertAvailableNow(context: context) + && PreferencesContainer.notifications.allowStreaksNotifications == false + && StepicApplicationsInfo.streaksEnabled + switch trigger { case .login: - return commonChecks && RemoteConfig.shared.showStreaksNotificationTrigger == .loginAndSubmission && streakAlertShownCnt == 0 + return commonChecks + && RemoteConfig.shared.showStreaksNotificationTrigger == .loginAndSubmission + && self.streakAlertShownCnt == 0 case .submission: - return commonChecks && streakAlertShownCnt < maxStreakAlertShownCnt + return commonChecks && self.streakAlertShownCnt < self.maxStreakAlertShownCnt } - case .notificationsTab, .courseSubscription: - return isAlertAvailableNow(context: context) && AuthInfo.shared.isAuthorized + case .notificationsTab, .courseSubscription, .default: + return self.isAlertAvailableNow(context: context) && AuthInfo.shared.isAuthorized } } } diff --git a/Stepic/NotificationsPresenter.swift b/Stepic/NotificationsPresenter.swift index f90a0c5307..2828243e0e 100644 --- a/Stepic/NotificationsPresenter.swift +++ b/Stepic/NotificationsPresenter.swift @@ -15,7 +15,6 @@ protocol NotificationsView: class { func set(notifications: NotificationViewDataStruct, withReload: Bool) func updateMarkAllAsReadButton(with status: NotificationsMarkAsReadButton.Status) - func present(alertManager: AlertManager, alert: UIViewController) } enum NotificationsViewState { @@ -39,16 +38,16 @@ extension NSNotification.Name { static let notificationAdded = NSNotification.Name("notificationAdded") } -class NotificationsPresenter { +final class NotificationsPresenter { weak var view: NotificationsView? - var notificationsAPI: NotificationsAPI - var usersAPI: UsersAPI - var notificationsStatusAPI: NotificationStatusesAPI - var notificationPermissionManager: NotificationPermissionManager - var notificationSuggestionManager: NotificationSuggestionManager + private let notificationsAPI: NotificationsAPI + private let usersAPI: UsersAPI + private let notificationsStatusAPI: NotificationStatusesAPI + private let notificationsRegistrationService: NotificationsRegistrationServiceProtocol + private let notificationSuggestionManager: NotificationSuggestionManager private var page = 1 - var hasNextPage = true + private var hasNextPage = true private var displayedNotifications: NotificationViewDataStruct = [] private var section: NotificationsSection = .all @@ -56,15 +55,25 @@ class NotificationsPresenter { // Store unread notifications count to pass it to analytics private var badgeUnreadCount = 0 - init(section: NotificationsSection, notificationsAPI: NotificationsAPI, usersAPI: UsersAPI, notificationsStatusAPI: NotificationStatusesAPI, notificationPermissionManager: NotificationPermissionManager, notificationSuggestionManager: NotificationSuggestionManager, view: NotificationsView) { + init( + section: NotificationsSection, + notificationsAPI: NotificationsAPI, + usersAPI: UsersAPI, + notificationsStatusAPI: NotificationStatusesAPI, + notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + notificationSuggestionManager: NotificationSuggestionManager, + view: NotificationsView + ) { self.section = section self.notificationsAPI = notificationsAPI self.usersAPI = usersAPI self.notificationsStatusAPI = notificationsStatusAPI - self.notificationPermissionManager = notificationPermissionManager + self.notificationsRegistrationService = notificationsRegistrationService self.notificationSuggestionManager = notificationSuggestionManager self.view = view + self.notificationsRegistrationService.delegate = self + NotificationCenter.default.addObserver(self, selector: #selector(self.didNotificationUpdate(systemNotification:)), name: .notificationUpdated, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.didAllNotificationsRead(systemNotification:)), name: .allNotificationsMarkedAsRead, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.didNotificationAdd(systemNotification:)), name: .notificationAdded, object: nil) @@ -130,20 +139,7 @@ class NotificationsPresenter { } func didAppear() { - if #available(iOS 10.0, *) { - if notificationSuggestionManager.canShowAlert(context: .notificationsTab) { - notificationPermissionManager.getCurrentPermissionStatus().done { [weak self] status in - switch status { - case .notDetermined: - let alert = Alerts.notificationRequest.construct(context: .notificationsTab) - self?.view?.present(alertManager: Alerts.notificationRequest, alert: alert) - self?.notificationSuggestionManager.didShowAlert(context: .notificationsTab) - default: - break - } - } - } - } + self.notificationsRegistrationService.registerForRemoteNotifications() } func refresh() { @@ -356,3 +352,23 @@ class NotificationsPresenter { } } } + +// MARK: - NotificationsPresenter: NotificationsRegistrationServiceDelegate - + +extension NotificationsPresenter: NotificationsRegistrationServiceDelegate { + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + shouldPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) -> Bool { + return self.notificationSuggestionManager.canShowAlert(context: .notificationsTab) + } + + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + didPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) { + if alertType == .permission { + self.notificationSuggestionManager.didShowAlert(context: .notificationsTab) + } + } +} diff --git a/Stepic/NotificationsRequestAlertDataSource.swift b/Stepic/NotificationsRequestAlertDataSource.swift new file mode 100644 index 0000000000..250d8576aa --- /dev/null +++ b/Stepic/NotificationsRequestAlertDataSource.swift @@ -0,0 +1,19 @@ +// +// NotificationsRequestAlertDataSource.swift +// Stepic +// +// Created by Ivan Magda on 29/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +protocol NotificationsRequestAlertDataSource: class { + var positiveAction: (() -> Void)? { get set } + var negativeAction: (() -> Void)? { get set } + + func alert( + for alertType: NotificationsRegistrationServiceAlertType, + in context: NotificationRequestAlertContext + ) -> UIViewController +} diff --git a/Stepic/NotificationsRequestAlertPresenter.swift b/Stepic/NotificationsRequestAlertPresenter.swift new file mode 100644 index 0000000000..7e0a95dfe5 --- /dev/null +++ b/Stepic/NotificationsRequestAlertPresenter.swift @@ -0,0 +1,76 @@ +// +// NotificationsRequestAlertPresenter.swift +// Stepic +// +// Created by Ivan Magda on 29/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit +import Presentr + +final class NotificationsRequestAlertPresenter: NotificationsRegistrationPresentationServiceProtocol { + var onPositiveCallback: (() -> Void)? + var onCancelCallback: (() -> Void)? + + private let context: NotificationRequestAlertContext + private let presentationType: PresentationType + private let dataSource: NotificationsRequestAlertDataSource + private let presentAlertIfRegistered: Bool + + private lazy var presentr: Presentr = { + let presentr = Presentr(presentationType: self.presentationType) + presentr.roundCorners = true + return presentr + }() + + init( + context: NotificationRequestAlertContext = .default, + presentationType: PresentationType = .dynamic(center: .center), + dataSource: NotificationsRequestAlertDataSource = CommonNotificationsRequestAlertDataSource(), + presentAlertIfRegistered: Bool = false + ) { + self.context = context + self.presentationType = presentationType + self.dataSource = dataSource + self.presentAlertIfRegistered = presentAlertIfRegistered + } + + func presentAlert( + for alertType: NotificationsRegistrationServiceAlertType, + inController controller: UIViewController + ) { + self.dataSource.positiveAction = self.onPositiveCallback + self.dataSource.negativeAction = self.onCancelCallback + + if self.presentAlertIfRegistered { + self.present(alertType: alertType, controller: controller) + } else { + NotificationPermissionStatus.current.done { [weak self] status in + if !status.isRegistered { + self?.present(alertType: alertType, controller: controller) + } + } + } + } + + private func present( + alertType: NotificationsRegistrationServiceAlertType, + controller: UIViewController + ) { + switch alertType { + case .permission: + let alert = self.dataSource.alert(for: .permission, in: self.context) + controller.customPresentViewController( + self.presentr, + viewController: alert, + animated: true + ) + case .settings: + controller.present( + self.dataSource.alert(for: .settings, in: self.context), + animated: true + ) + } + } +} diff --git a/Stepic/NotificationsRequestOnlySettingsAlertPresenter.swift b/Stepic/NotificationsRequestOnlySettingsAlertPresenter.swift new file mode 100644 index 0000000000..08edcf4623 --- /dev/null +++ b/Stepic/NotificationsRequestOnlySettingsAlertPresenter.swift @@ -0,0 +1,42 @@ +// +// NotificationsRequestOnlySettingsAlertPresenter.swift +// Stepic +// +// Created by Ivan Magda on 29/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +final class NotificationsRequestOnlySettingsAlertPresenter: NotificationsRegistrationPresentationServiceProtocol { + var onPositiveCallback: (() -> Void)? + var onCancelCallback: (() -> Void)? + + private let context: NotificationRequestAlertContext + private let dataSource: NotificationsRequestAlertDataSource + + init(context: NotificationRequestAlertContext = .default, + dataSource: NotificationsRequestAlertDataSource = CommonNotificationsRequestAlertDataSource() + ) { + self.context = context + self.dataSource = dataSource + } + + func presentAlert( + for alertType: NotificationsRegistrationServiceAlertType, + inController controller: UIViewController + ) { + switch alertType { + case .permission: + self.onPositiveCallback?() + case .settings: + self.dataSource.positiveAction = self.onPositiveCallback + self.dataSource.negativeAction = self.onCancelCallback + + controller.present( + self.dataSource.alert(for: .settings, in: self.context), + animated: true + ) + } + } +} diff --git a/Stepic/NotificationsViewController.swift b/Stepic/NotificationsViewController.swift index 78a0c9198f..a1d2b566bc 100644 --- a/Stepic/NotificationsViewController.swift +++ b/Stepic/NotificationsViewController.swift @@ -73,7 +73,18 @@ class NotificationsViewController: UIViewController, NotificationsView { } markAllAsReadButton.setTitle(NSLocalizedString("MarkAllAsRead", comment: ""), for: .normal) - presenter = NotificationsPresenter(section: section, notificationsAPI: ApiDataDownloader.notifications, usersAPI: ApiDataDownloader.users, notificationsStatusAPI: NotificationStatusesAPI(), notificationPermissionManager: NotificationPermissionManager(), notificationSuggestionManager: NotificationSuggestionManager(), view: self) + presenter = NotificationsPresenter( + section: section, + notificationsAPI: ApiDataDownloader.notifications, + usersAPI: ApiDataDownloader.users, + notificationsStatusAPI: NotificationStatusesAPI(), + notificationsRegistrationService: NotificationsRegistrationService( + presenter: NotificationsRequestAlertPresenter(context: .notificationsTab), + analytics: .init(source: .notificationsTab) + ), + notificationSuggestionManager: NotificationSuggestionManager(), + view: self + ) tableView.register(UINib(nibName: "NotificationsTableViewCell", bundle: nil), forCellReuseIdentifier: NotificationsTableViewCell.reuseId) tableView.register(UINib(nibName: "NotificationsSectionHeaderView", bundle: nil), forHeaderFooterViewReuseIdentifier: NotificationsSectionHeaderView.reuseId) @@ -110,10 +121,6 @@ class NotificationsViewController: UIViewController, NotificationsView { } } - func present(alertManager: AlertManager, alert: UIViewController) { - alertManager.present(alert: alert, inController: self) - } - @objc func refreshNotifications() { if state == .loading || state == .refreshing { return diff --git a/Stepic/OnboardingViewController.swift b/Stepic/OnboardingViewController.swift index f7d8edfdf5..50ca3b1829 100644 --- a/Stepic/OnboardingViewController.swift +++ b/Stepic/OnboardingViewController.swift @@ -196,17 +196,8 @@ class OnboardingViewController: UIViewController { AmplitudeAnalyticsEvents.Onboarding.completed.send() dismiss(animated: true, completion: nil) - if AuthAfterOnboardingSplitTest.shouldParticipate { - let authSplitTest = splitTestingService.fetchSplitTest(AuthAfterOnboardingSplitTest.self) - if authSplitTest.currentGroup.shouldShowAuth { - if let authSource = authSource { - RoutingManager.auth.routeFrom(controller: authSource, success: nil, cancel: nil) - } - } - } else { - if let authSource = authSource { - RoutingManager.auth.routeFrom(controller: authSource, success: nil, cancel: nil) - } + if let authSource = authSource { + RoutingManager.auth.routeFrom(controller: authSource, success: nil, cancel: nil) } } } diff --git a/Stepic/ProfilePresenter.swift b/Stepic/ProfilePresenter.swift index b8a162da29..8c238c4770 100644 --- a/Stepic/ProfilePresenter.swift +++ b/Stepic/ProfilePresenter.swift @@ -12,7 +12,6 @@ import PromiseKit protocol ProfileView: class { func set(state: ProfileState) - func requestNotificationsPermissions() func showStreakTimeSelection(startHour: Int) func showAchievementInfo(viewData: AchievementViewData, canShare: Bool) @@ -61,7 +60,6 @@ class ProfilePresenter { private var userActivitiesAPI: UserActivitiesAPI private var usersAPI: UsersAPI - private var notificationPermissionManager: NotificationPermissionManager private var userSeed: UserSeed @@ -72,11 +70,10 @@ class ProfilePresenter { .description] private static let otherUserMenu: [ProfileMenuBlock] = [.infoHeader, .pinsMap, .achievements, .description] - init(userSeed: UserSeed, view: ProfileView, userActivitiesAPI: UserActivitiesAPI, usersAPI: UsersAPI, notificationPermissionManager: NotificationPermissionManager) { + init(userSeed: UserSeed, view: ProfileView, userActivitiesAPI: UserActivitiesAPI, usersAPI: UsersAPI) { self.view = view self.userActivitiesAPI = userActivitiesAPI self.usersAPI = usersAPI - self.notificationPermissionManager = notificationPermissionManager self.userSeed = userSeed } diff --git a/Stepic/ProfileViewController+StreakNotificationsControlView.swift b/Stepic/ProfileViewController+StreakNotificationsControlView.swift index 04b42515f4..30764de2f9 100644 --- a/Stepic/ProfileViewController+StreakNotificationsControlView.swift +++ b/Stepic/ProfileViewController+StreakNotificationsControlView.swift @@ -24,17 +24,6 @@ extension ProfileViewController: StreakNotificationsControlView { customPresentViewController(streakTimePickerPresentr, viewController: vc, animated: true, completion: nil) } - func requestNotificationsPermissions() { - let alert = UIAlertController(title: NSLocalizedString("StreakNotificationsAlertTitle", comment: ""), message: NSLocalizedString("StreakNotificationsAlertMessage", comment: ""), preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("Yes", comment: ""), style: .default, handler: { _ in - UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!) - })) - - alert.addAction(UIAlertAction(title: NSLocalizedString("No", comment: ""), style: .cancel, handler: nil)) - - self.present(alert, animated: true) - } - func updateDisplayedStreakTime(startHour: Int) { func getDisplayingStreakTimeInterval(startHour: Int) -> String { let startInterval = TimeInterval((startHour % 24) * 60 * 60) @@ -52,4 +41,30 @@ extension ProfileViewController: StreakNotificationsControlView { menu?.update(block: block) } } + + func setNotificationsSwitchIsOn(_ isOn: Bool) { + let id = ProfileMenuBlock.notificationsSwitch(isOn: isOn).rawValue + guard let block = self.menu?.getBlock(id: id) as? SwitchMenuBlock else { + return + } + + if isOn { + guard let timeSelectionBlock = self.buildNotificationsTimeSelectionBlock() else { + self.presenterNotifications?.setStreakNotifications(on: false) + return + } + + self.menu?.insert( + block: timeSelectionBlock, + afterBlockWithId: ProfileMenuBlock.notificationsSwitch(isOn: isOn).rawValue + ) + + self.presenterNotifications?.refreshStreakNotificationTime() + } else { + self.menu?.remove(id: ProfileMenuBlock.notificationsTimeSelection.rawValue) + } + + block.isOn = isOn + self.menu?.update(block: block) + } } diff --git a/Stepic/ProfileViewController.swift b/Stepic/ProfileViewController.swift index 3f0a0c37ea..ba3ccb7c59 100644 --- a/Stepic/ProfileViewController.swift +++ b/Stepic/ProfileViewController.swift @@ -7,7 +7,6 @@ // import UIKit -import Presentr class ProfileViewController: MenuViewController, ProfileView, ControllerWithStepikPlaceholder { var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() @@ -178,7 +177,7 @@ class ProfileViewController: MenuViewController, ProfileView, ControllerWithStep seed = .other(id: userId) } - presenter = ProfilePresenter(userSeed: seed, view: self, userActivitiesAPI: UserActivitiesAPI(), usersAPI: UsersAPI(), notificationPermissionManager: NotificationPermissionManager()) + presenter = ProfilePresenter(userSeed: seed, view: self, userActivitiesAPI: UserActivitiesAPI(), usersAPI: UsersAPI()) presenter?.refresh(shouldReload: true) } @@ -228,23 +227,15 @@ class ProfileViewController: MenuViewController, ProfileView, ControllerWithStep } private func buildNotificationsSwitchBlock(isOn: Bool) -> SwitchMenuBlock { - let block: SwitchMenuBlock = SwitchMenuBlock(id: ProfileMenuBlock.notificationsSwitch(isOn: false).rawValue, title: NSLocalizedString("NotifyAboutStreaksPreference", comment: ""), isOn: isOn) + let block = SwitchMenuBlock( + id: ProfileMenuBlock.notificationsSwitch(isOn: false).rawValue, + title: NSLocalizedString("NotifyAboutStreaksPreference", comment: ""), + isOn: isOn + ) block.onSwitch = { [weak self] isOn in self?.presenterNotifications?.setStreakNotifications(on: isOn) { [weak self] status in - if status { - guard let timeSelectionBlock = self?.buildNotificationsTimeSelectionBlock() else { - self?.presenterNotifications?.setStreakNotifications(on: !isOn) - return - } - - block.isOn = true - self?.menu?.insert(block: timeSelectionBlock, afterBlockWithId: ProfileMenuBlock.notificationsSwitch(isOn: false).rawValue) - self?.presenterNotifications?.refreshStreakNotificationTime() - } else { - block.isOn = false - self?.menu?.remove(id: ProfileMenuBlock.notificationsTimeSelection.rawValue) - } + self?.setNotificationsSwitchIsOn(status) } } @@ -274,7 +265,7 @@ class ProfileViewController: MenuViewController, ProfileView, ControllerWithStep return block } - private func buildNotificationsTimeSelectionBlock() -> TransitionMenuBlock? { + func buildNotificationsTimeSelectionBlock() -> TransitionMenuBlock? { var currentZone00UTC: String { let date = Date(timeIntervalSince1970: 0) let dateFormatter = DateFormatter() diff --git a/Stepic/QuizPresenter.swift b/Stepic/QuizPresenter.swift index a2f0331e27..00f0c731f4 100644 --- a/Stepic/QuizPresenter.swift +++ b/Stepic/QuizPresenter.swift @@ -420,9 +420,6 @@ class QuizPresenter { return } - #if !os(tvOS) - self?.streaksNotificationSuggestionManager?.didShowAlert(context: .streak) - #endif self?.view?.suggestStreak(streak: activity.currentStreak) }, error: { _ in diff --git a/Stepic/QuizViewController.swift b/Stepic/QuizViewController.swift index cdf4569f63..86b4109529 100644 --- a/Stepic/QuizViewController.swift +++ b/Stepic/QuizViewController.swift @@ -7,7 +7,6 @@ // import UIKit -import Presentr import SnapKit class QuizViewController: UIViewController, QuizView, QuizControllerDataSource, ControllerWithStepikPlaceholder { @@ -344,7 +343,8 @@ class QuizViewController: UIViewController, QuizView, QuizControllerDataSource, } func suggestStreak(streak: Int) { - streaksAlertPresentationManager.suggestStreak(streak: streak) + self.streaksAlertPresentationManager.controller = self + self.streaksAlertPresentationManager.suggestStreak(streak: streak) } func showRateAlert() { diff --git a/Stepic/RegistrationPresenter.swift b/Stepic/RegistrationPresenter.swift index ca438b9040..1829c55bd1 100644 --- a/Stepic/RegistrationPresenter.swift +++ b/Stepic/RegistrationPresenter.swift @@ -49,7 +49,7 @@ class RegistrationPresenter { AuthInfo.shared.token = token AuthInfo.shared.authorizationType = authorizationType - NotificationRegistrator.shared.registerForRemoteNotificationsIfAlreadyAsked() + NotificationsRegistrationService().renewDeviceToken() return self.stepicsAPI.retrieveCurrentUser() }.then { user -> Promise in diff --git a/Stepic/SearchResultsAPI.swift b/Stepic/SearchResultsAPI.swift index ea274b450b..d59aeb8b3a 100644 --- a/Stepic/SearchResultsAPI.swift +++ b/Stepic/SearchResultsAPI.swift @@ -16,10 +16,13 @@ class SearchResultsAPI: APIEndpoint { @available(*, deprecated, message: "Use searchCourse() -> Promise<([SearchResult], Meta)> instead") @discardableResult func search(query: String, type: String?, language: ContentLanguage, page: Int?, headers: [String: String] = AuthInfo.shared.initialHTTPHeaders, success: @escaping ([SearchResult], Meta) -> Void, error errorHandler: @escaping (NSError) -> Void) -> Request? { - var params: Parameters = [:] - - params["access_token"] = AuthInfo.shared.token?.accessToken as NSObject? - params["query"] = query.lowercased() + var params: Parameters = [ + "query" : query.lowercased(), + "access_token": AuthInfo.shared.token?.accessToken ?? "", + "language": language.searchCoursesParameter ?? "", + "is_popular": "true", + "is_public": "true" + ] if let p = page { params["page"] = p @@ -27,7 +30,6 @@ class SearchResultsAPI: APIEndpoint { if let t = type { params["type"] = t } - params["language"] = language.searchCoursesParameter return manager.request("\(StepicApplicationsInfo.apiURL)/search-results", method: .get, parameters: params, encoding: URLEncoding.default, headers: headers).responseSwiftyJSON({ response in diff --git a/Stepic/SectionsViewController.swift b/Stepic/SectionsViewController.swift index b3f1789e9f..63adff18ca 100644 --- a/Stepic/SectionsViewController.swift +++ b/Stepic/SectionsViewController.swift @@ -29,12 +29,23 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr var isFirstLoad: Bool = true private let notificationSuggestionManager = NotificationSuggestionManager() - private let notificationPermissionManager = NotificationPermissionManager() + private lazy var notificationsRegistrationService: NotificationsRegistrationServiceProtocol = { + NotificationsRegistrationService( + delegate: self, + presenter: NotificationsRequestAlertPresenter(context: .courseSubscription), + analytics: .init(source: .courseSubscription) + ) + }() override func viewDidLoad() { super.viewDidLoad() - registerPlaceholder(placeholder: StepikPlaceholder(.noConnection), for: .connectionError) + self.registerPlaceholder( + placeholder: StepikPlaceholder(.noConnection, action: { [weak self] in + self?.refreshSections() + }), + for: .connectionError + ) LastStepGlobalContext.context.course = course @@ -118,6 +129,10 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr modesVC.onDeadlineSelected = { [weak self] in self?.tableView.reloadData() + NotificationsRegistrationService( + presenter: NotificationsRequestOnlySettingsAlertPresenter(), + analytics: .init(source: .personalDeadline) + ).registerForRemoteNotifications() } customPresentViewController(presentr, viewController: modesVC, animated: true, completion: nil) } @@ -238,45 +253,13 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr }, error: {}) } - let shareTooltipBlock = { - [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.shareTooltip = TooltipFactory.sharingCourse - strongSelf.shareTooltip?.show(direction: .up, in: nil, from: strongSelf.shareBarButtonItem) - strongSelf.didJustSubscribe = false - } - if didJustSubscribe { - if #available(iOS 10.0, *) { - if notificationSuggestionManager.canShowAlert(context: .courseSubscription) { - notificationPermissionManager.getCurrentPermissionStatus().done { - [weak self] - status in - guard let strongSelf = self else { - return - } - switch status { - case .notDetermined: - let alert = Alerts.notificationRequest.construct(context: .courseSubscription) - alert.yesAction = { - NotificationRegistrator.shared.registerForRemoteNotifications() - shareTooltipBlock() - } - Alerts.notificationRequest.present(alert: alert, inController: strongSelf) - strongSelf.notificationSuggestionManager.didShowAlert(context: .courseSubscription) - return - default: - shareTooltipBlock() - break - } - } + NotificationPermissionStatus.current.done { status in + if status == .notDetermined { + self.notificationsRegistrationService.registerForRemoteNotifications() } else { - shareTooltipBlock() + self.showShareTooltip() } - } else { - shareTooltipBlock() } } } @@ -331,11 +314,6 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr }) } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "showCourse" { let dvc = segue.destination as! CoursePreviewViewController @@ -349,15 +327,14 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr } } - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { - // Get the new view controller using segue.destinationViewController. - // Pass the selected object to the new view controller. + private func showShareTooltip() { + self.shareTooltip?.dismiss() + self.shareTooltip = TooltipFactory.sharingCourse + self.shareTooltip?.show(direction: .up, in: nil, from: self.shareBarButtonItem) + self.didJustSubscribe = false } - */ + + // MARK: - Navigation func showExamAlert(cancel cancelAction: @escaping (() -> Void)) { let alert = UIAlertController(title: NSLocalizedString("ExamTitle", comment: ""), message: NSLocalizedString("ShowExamInWeb", comment: ""), preferredStyle: .alert) @@ -689,3 +666,29 @@ extension SectionsViewController : PKDownloadButtonDelegate { } } } + +// MARK: - SectionsViewController: NotificationsRegistrationServiceDelegate - + +extension SectionsViewController: NotificationsRegistrationServiceDelegate { + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + shouldPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) -> Bool { + let canShowAlert = self.notificationSuggestionManager.canShowAlert(context: .courseSubscription) + + if !canShowAlert { + self.showShareTooltip() + } + + return canShowAlert + } + + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + didPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) { + if alertType == .permission { + self.notificationSuggestionManager.didShowAlert(context: .courseSubscription) + } + } +} diff --git a/Stepic/Services/Notifications/LocalNotificationContentProvider.swift b/Stepic/Services/Notifications/LocalNotifications/LocalNotificationContentProvider.swift similarity index 100% rename from Stepic/Services/Notifications/LocalNotificationContentProvider.swift rename to Stepic/Services/Notifications/LocalNotifications/LocalNotificationContentProvider.swift diff --git a/Stepic/Services/Notifications/LocalNotificationsMigrator.swift b/Stepic/Services/Notifications/LocalNotifications/LocalNotificationsMigrator.swift similarity index 100% rename from Stepic/Services/Notifications/LocalNotificationsMigrator.swift rename to Stepic/Services/Notifications/LocalNotifications/LocalNotificationsMigrator.swift diff --git a/Stepic/Services/Notifications/LocalNotificationsService.swift b/Stepic/Services/Notifications/LocalNotifications/LocalNotificationsService.swift similarity index 100% rename from Stepic/Services/Notifications/LocalNotificationsService.swift rename to Stepic/Services/Notifications/LocalNotifications/LocalNotificationsService.swift diff --git a/Stepic/Services/Notifications/NotificationsService.swift b/Stepic/Services/Notifications/NotificationsService.swift index 3ee5c0b9ef..d879c27ed9 100644 --- a/Stepic/Services/Notifications/NotificationsService.swift +++ b/Stepic/Services/Notifications/NotificationsService.swift @@ -15,6 +15,7 @@ final class NotificationsService { typealias NotificationUserInfo = [AnyHashable: Any] private let localNotificationsService: LocalNotificationsService + private let notificationsRegistrationService: NotificationsRegistrationServiceProtocol private let deepLinkRoutingService: DeepLinkRoutingService private var isInForeground: Bool { @@ -23,13 +24,19 @@ final class NotificationsService { init( localNotificationsService: LocalNotificationsService = LocalNotificationsService(), + notificationsRegistrationService: NotificationsRegistrationServiceProtocol = NotificationsRegistrationService(), deepLinkRoutingService: DeepLinkRoutingService = DeepLinkRoutingService() ) { self.localNotificationsService = localNotificationsService + self.notificationsRegistrationService = notificationsRegistrationService self.deepLinkRoutingService = deepLinkRoutingService } func handleLaunchOptions(_ launchOptions: [UIApplicationLaunchOptionsKey: Any]?) { + NotificationPermissionStatus.current.done { status in + AnalyticsUserProperties.shared.setPushPermissionStatus(status) + } + if let localNotification = launchOptions?[.localNotification] as? UILocalNotification { self.handleLocalNotification(with: localNotification.userInfo) AmplitudeAnalyticsEvents.Launch.sessionStart( @@ -65,9 +72,9 @@ extension NotificationsService { with contentProvider: LocalNotificationContentProvider, removeIdentical: Bool = true ) { - NotificationPermissionManager().getCurrentPermissionStatus().then { status -> Promise in + NotificationPermissionStatus.current.then { status -> Promise in if !status.isRegistered { - NotificationRegistrator.shared.registerForRemoteNotifications() + self.notificationsRegistrationService.renewDeviceToken() } if removeIdentical { @@ -163,7 +170,7 @@ extension NotificationsService { } guard let aps = userInfo[PayloadKey.aps.rawValue] as? [String: Any], - let alert = aps[PayloadKey.alert.rawValue] as? [String: Any], + let alert = aps[PayloadKey.alert.rawValue] as? [String: Any], let body = alert[PayloadKey.body.rawValue] as? String, let object = userInfo[PayloadKey.object.rawValue] as? String else { return print("remote notification received: unable to parse notification: \(userInfo)") diff --git a/Stepic/Services/Notifications/Registration/NotificationsRegistrationService.swift b/Stepic/Services/Notifications/Registration/NotificationsRegistrationService.swift new file mode 100644 index 0000000000..b0c04befca --- /dev/null +++ b/Stepic/Services/Notifications/Registration/NotificationsRegistrationService.swift @@ -0,0 +1,390 @@ +// +// NotificationsRegistrationService.swift +// Stepic +// +// Created by Ivan Magda on 19/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import FirebaseMessaging +import FirebaseCore +import FirebaseInstanceID +import PromiseKit +import UserNotifications + +final class NotificationsRegistrationService: NotificationsRegistrationServiceProtocol { + weak var delegate: NotificationsRegistrationServiceDelegate? + var presenter: NotificationsRegistrationPresentationServiceProtocol? + private var analytics: NotificationAlertsAnalytics? + + init( + delegate: NotificationsRegistrationServiceDelegate? = nil, + presenter: NotificationsRegistrationPresentationServiceProtocol? = nil, + analytics: NotificationAlertsAnalytics? = nil + ) { + self.delegate = delegate + self.presenter = presenter + self.analytics = analytics + } + + // MARK: - Handling APNs pipeline events - + + func handleDeviceToken(_ deviceToken: Data) { + print("NotificationsRegistrationService: did register for remote notifications") + self.getGCMRegistrationToken(deviceToken: deviceToken) + self.postCurrentPermissionStatus() + self.delegate?.notificationsRegistrationServiceDidSuccessfullyRegisterWithAPNs(self) + } + + func handleRegistrationError(_ error: Error) { + print("NotificationsRegistrationService: did fail register with error: \(error)") + self.postCurrentPermissionStatus() + self.delegate?.notificationsRegistrationServiceDidFailRegisterWithAPNs(self, error: error) + } + + func handleRegisteredNotificationSettings(_ notificationSettings: UIUserNotificationSettings) { + print("NotificationsRegistrationService: registered with settings: \(notificationSettings)") + + let granted = notificationSettings.types != [] + + if self.isFirstRegistrationIsInProgress { + self.isFirstRegistrationIsInProgress = false + self.analytics?.reportDefaultAlertInteractionResult(granted ? .yes : .no) + } + + if granted { + self.registerWithAPNs() + } else { + self.postCurrentPermissionStatus() + } + } + + // MARK: - Register - + + func renewDeviceToken() { + self.register(forceToRequestAuthorization: false) + } + + func registerForRemoteNotifications() { + self.register(forceToRequestAuthorization: true) + } + + /// Initiates the registration pipeline. + /// + /// - Parameter forceToRequestAuthorization: The flag that indicates whether to request notifications + /// permission directly or only if already has granted permissions. + /// + /// If the `forceToRequestAuthorization` parameter is `true` then the user will be prompted for + /// notifications permissions directly otherwise firstly will check if has granted permissions. + private func register(forceToRequestAuthorization: Bool) { + guard AuthInfo.shared.isAuthorized else { + return + } + + self.postCurrentPermissionStatus() + + if forceToRequestAuthorization { + self.register() + } else { + self.registerIfAuthorized() + } + } + + private func registerIfAuthorized() { + if #available(iOS 10.0, *) { + NotificationPermissionStatus.current.done { status in + if status.isRegistered { + self.register() + } + } + } else { + self.register() + } + } + + private func register() { + defer { + self.fetchFirebaseAppInstanceID() + } + + guard StepicApplicationsInfo.shouldRegisterNotifications else { + return + } + + let isDelegateAllow = self.delegate?.notificationsRegistrationService(self, + shouldPresentAlertFor: .permission) ?? false + let shouldPresentCustomPermissionAlert = self.presenter != nil && isDelegateAllow + + if shouldPresentCustomPermissionAlert { + self.presentPermissionAlert() + } else if #available(iOS 10.0, *) { + NotificationPermissionStatus.current.done { status in + if status == .denied { + self.presentSettingsAlertIfNeeded() + } else { + self.presentPermissionAlertIfNeeded() + } + } + } else { + self.presentPermissionAlertIfNeeded() + } + } + + /// Prompts the user to authorize with desired notifications settings. + private func requestAuthorization() { + if !self.didShowDefaultPermissionAlert { + self.analytics?.reportDefaultAlertShown() + self.isFirstRegistrationIsInProgress = true + } + + self.didShowDefaultPermissionAlert = true + + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().requestAuthorization( + options: [.alert, .badge, .sound], + completionHandler: { granted, error in + if self.isFirstRegistrationIsInProgress { + self.isFirstRegistrationIsInProgress = false + self.analytics?.reportDefaultAlertInteractionResult(granted ? .yes : .no) + } + + if granted { + self.registerWithAPNs() + } else if let error = error { + print("NotificationsRegistrationService: did fail request authorization with error: \(error)") + } + } + ) + } else { + let notificationSettings = UIUserNotificationSettings( + types: [.alert, .badge, .sound], + categories: nil + ) + UIApplication.shared.registerUserNotificationSettings(notificationSettings) + } + } + + /// Initiates the registration process with Apple Push Notification service. + private func registerWithAPNs() { + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + + private func presentPermissionAlertIfNeeded() { + if self.didShowDefaultPermissionAlert || self.presenter == nil { + self.requestAuthorization() + } else { + self.presentPermissionAlert() + } + } + + private func presentPermissionAlert() { + self.presenter?.onPositiveCallback = { + self.analytics?.reportCustomAlertInteractionResult(.yes) + NotificationPermissionStatus.current.done { status in + if status == .denied { + self.presentSettingsAlert() + } else { + self.requestAuthorization() + } + } + } + self.presenter?.onCancelCallback = { + self.analytics?.reportCustomAlertInteractionResult(.no) + } + + self.presentAlert(for: .permission) + } + + private func presentSettingsAlertIfNeeded() { + if let delegate = self.delegate { + if delegate.notificationsRegistrationService(self, shouldPresentAlertFor: .settings) { + self.presentSettingsAlert() + } + } else { + self.presentSettingsAlert() + } + } + + private func presentSettingsAlert() { + self.presenter?.onPositiveCallback = { + self.analytics?.reportCustomAlertInteractionResult(.yes) + + if let settingsURL = URL(string: UIApplicationOpenSettingsURLString) { + UIApplication.shared.openURL(settingsURL) + } + } + self.presenter?.onCancelCallback = { + self.analytics?.reportCustomAlertInteractionResult(.no) + } + + self.presentAlert(for: .settings) + } + + private func presentAlert(for type: NotificationsRegistrationServiceAlertType) { + guard let rootViewController = SourcelessRouter().window?.rootViewController else { + return + } + + DispatchQueue.main.async { + self.presenter?.presentAlert(for: type, inController: rootViewController) + self.delegate?.notificationsRegistrationService(self, didPresentAlertFor: type) + } + } + + // MARK: Device + + func registerDevice(_ registrationToken: String, forceCreation: Bool) { + let newDevice = Device( + registrationId: registrationToken, + deviceDescription: DeviceInfo.current.deviceInfoString + ) + + //TODO: Remove this after refactoring errors + checkToken().then { _ -> Promise in + if let savedDeviceId = DeviceDefaults.sharedDefaults.deviceId, !forceCreation { + print("NotificationsRegistrationService: retrieve device by saved deviceId = \(savedDeviceId)") + return ApiDataDownloader.devices.retrieve(deviceId: savedDeviceId) + } else { + return ApiDataDownloader.devices.create(newDevice) + } + }.then { remoteDevice -> Promise in + if remoteDevice.isBadgesEnabled { + return .value(remoteDevice) + } else { + remoteDevice.isBadgesEnabled = true + return ApiDataDownloader.devices.update(remoteDevice) + } + }.done { device -> Void in + print("NotificationsRegistrationService: device registered, info = \(device.json)") + DeviceDefaults.sharedDefaults.deviceId = device.id + }.catch { error in + switch error { + case DeviceError.notFound: + print("NotificationsRegistrationService: device not found, create new") + self.registerDevice(registrationToken, forceCreation: true) + case DeviceError.other(_, _, let message): + print("NotificationsRegistrationService: device registration error, error = \(String(describing: message))") + AnalyticsReporter.reportEvent( + AnalyticsEvents.Errors.registerDevice, + parameters: ["message": "\(String(describing: message))"] + ) + default: + print("NotificationsRegistrationService: device registration error, error = \(error)") + AnalyticsReporter.reportEvent( + AnalyticsEvents.Errors.registerDevice, + parameters: ["message": "\(error.localizedDescription)"] + ) + } + } + } + + // MARK: - Firebase - + + private func getGCMRegistrationToken(deviceToken: Data) { + Messaging.messaging().apnsToken = deviceToken + } + + private func fetchFirebaseAppInstanceID() { + InstanceID.instanceID().instanceID { (result, error) in + if let error = error { + print("NotificationsRegistrationService: error while fetching Firebase remote instance ID: \(error)") + } else if let result = result { + self.registerDevice(result.token) + } + } + } + + // MARK: - Unregister - + + @available(*, deprecated, message: "Legacy method with callbacks") + func unregisterFromNotifications(completion: @escaping (() -> Void)) { + self.unregisterFromNotifications().done { + completion() + }.catch { _ in + } + } + + func unregisterFromNotifications() -> Guarantee { + return Guarantee { seal in + UIApplication.shared.unregisterForRemoteNotifications() + + if let deviceId = DeviceDefaults.sharedDefaults.deviceId { + ApiDataDownloader.devices.delete(deviceId).done { () in + print("NotificationsRegistrationService: successfully delete device, id = \(deviceId)") + seal(()) + }.catch { error in + switch error { + case DeviceError.notFound: + print("NotificationsRegistrationService: device not found on deletion, id = \(deviceId)") + default: + if let userId = AuthInfo.shared.userId, + let token = AuthInfo.shared.token { + + let deleteTask = DeleteDeviceExecutableTask(userId: userId, deviceId: deviceId) + ExecutionQueues.sharedQueues.connectionAvailableExecutionQueue.push(deleteTask) + + let userPersistencyManager = PersistentUserTokenRecoveryManager(baseName: "Users") + userPersistencyManager.writeStepicToken(token, userId: userId) + + let taskPersistencyManager = PersistentTaskRecoveryManager(baseName: "Tasks") + taskPersistencyManager.writeTask(deleteTask, name: deleteTask.id) + + let queuePersistencyManager = PersistentQueueRecoveryManager(baseName: "Queues") + queuePersistencyManager.writeQueue( + ExecutionQueues.sharedQueues.connectionAvailableExecutionQueue, + key: ExecutionQueues.sharedQueues.connectionAvailableExecutionQueueKey + ) + } else { + print("NotificationsRegistrationService: could not get current user ID or token to delete device") + AnalyticsReporter.reportEvent( + AnalyticsEvents.Errors.unregisterDeviceInvalidCredentials + ) + } + } + seal(()) + } + } else { + print("NotificationsRegistrationService: no saved device") + seal(()) + } + } + } +} + +// MARK: - NotificationsRegistrationService (UserDefaults) - + +extension NotificationsRegistrationService { + private static let didShowDefaultPermissionAlertKey = "didShowDefaultPermissionAlertKey" + private static let isFirstRegistrationIsInProgressKey = "isFirstRegistrationIsInProgressKey" + + private var didShowDefaultPermissionAlert: Bool { + get { + return UserDefaults.standard.bool( + forKey: NotificationsRegistrationService.didShowDefaultPermissionAlertKey + ) + } + set { + UserDefaults.standard.set( + newValue, + forKey: NotificationsRegistrationService.didShowDefaultPermissionAlertKey + ) + } + } + + private var isFirstRegistrationIsInProgress: Bool { + get { + return UserDefaults.standard.bool( + forKey: NotificationsRegistrationService.isFirstRegistrationIsInProgressKey + ) + } + set { + UserDefaults.standard.set( + newValue, + forKey: NotificationsRegistrationService.isFirstRegistrationIsInProgressKey + ) + } + } +} diff --git a/Stepic/Services/Notifications/Registration/NotificationsRegistrationServiceProtocol.swift b/Stepic/Services/Notifications/Registration/NotificationsRegistrationServiceProtocol.swift new file mode 100644 index 0000000000..8f3e6039f5 --- /dev/null +++ b/Stepic/Services/Notifications/Registration/NotificationsRegistrationServiceProtocol.swift @@ -0,0 +1,175 @@ +// +// NotificationsRegistrationServiceProtocol.swift +// Stepic +// +// Created by Ivan Magda on 26/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +/// The centralized point for registration with Apple Push Notifications service (APNs). +protocol NotificationsRegistrationServiceProtocol: class { + /// A set of methods that are called by the instance of the + /// `NotificationsRegistrationServiceProtocol` object in response to lifetime events. + var delegate: NotificationsRegistrationServiceDelegate? { get set } + + /// Responsible for custom alerts presentation. + /// + /// There are two types of alerts that may be presented: + /// - permission + /// - settings + /// + /// See `NotificationsRegistrationServiceAlertType`. + var presenter: NotificationsRegistrationPresentationServiceProtocol? { get set } + + /// Register to receive remote notifications via APNs. + /// Registration process with APNs will start only when user has already granted permissions. + /// Call this method each time when app launches. + func renewDeviceToken() + + /// Register to receive remote notifications via APNs. + /// Call this method to initiate the registration process with Apple Push Notification service. + func registerForRemoteNotifications() + + /// Tells that the app successfully registered with Apple Push Notification service (APNs). + /// + /// - Parameter deviceToken: A globally unique token that identifies this device to APNs. + func handleDeviceToken(_ deviceToken: Data) + + /// Tells that Apple Push Notification service cannot successfully complete the registration process. + /// + /// - Parameter error: Encapsulates information why registration did not succeed. + func handleRegistrationError(_ error: Error) + + /// Tells that the types of local and remote notifications that can be used to get the user’s attention. + /// + /// - Parameter notificationSettings: The user’s specified notification settings. + func handleRegisteredNotificationSettings(_ notificationSettings: UIUserNotificationSettings) + + /// Register the device with our notification provider server. + /// + /// - Parameters: + /// - registrationToken: An Firebase Messaging scoped token for the firebase app. + /// - forceCreation: Controls whether to create a new device instance or get cached one. + func registerDevice(_ registrationToken: String, forceCreation: Bool) +} + +extension NotificationsRegistrationServiceProtocol { + func registerDevice(_ registrationToken: String) { + self.registerDevice(registrationToken, forceCreation: false) + } +} + +/// Represents types of alerts that may be presented by the presenter. +/// +/// - permission: Custom alert for asking some kind of permission from the user +/// (Remote notifications, streak notifications, personal deadlines). +/// - settings: Custom alert that prompts user to go to the application settings to enable notifications. +enum NotificationsRegistrationServiceAlertType { + case permission + case settings +} + +/// The presentation layer of the notifications registration service. +/// It's responsible for preparation and presentations of the custom alerts. +protocol NotificationsRegistrationPresentationServiceProtocol { + var onPositiveCallback: (() -> Void)? { get set } + var onCancelCallback: (() -> Void)? { get set } + + /// The main point for presenting custom alerts. + /// + /// - Parameters: + /// - alertType: The purpose of presenting: `settings` or `permission`. (See `NotificationsRegistrationServiceAlertType`) + /// - controller: From view controller to present. + func presentAlert( + for alertType: NotificationsRegistrationServiceAlertType, + inController controller: UIViewController + ) +} + +/// The delegate of a `NotificationsRegistrationServiceProtocol` object must adopt the `NotificationsRegistrationServiceDelegate` protocol. +/// Methods of the protocol allow the delegate to manage presenting alerts and respond to the lifetime events. +protocol NotificationsRegistrationServiceDelegate: class { + /// Asks the delegate if the alert should be shown. + /// + /// - Parameters: + /// - notificationsRegistrationService: The NotificationsRegistrationService object that is making this request. + /// - alertType: An type of the alert that was requested. + /// - Returns: `true` if the notificationsRegistrationService should show alert, otherwise false. The default value is true. + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + shouldPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) -> Bool + + /// Tells the delegate that the alert is now was presented and now should be visible. + /// + /// - Parameters: + /// - notificationsRegistrationService: The NotificationsRegistrationService object informing the delegate about the alert presenting. + /// - alertType: An type of the alert that was presented. + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + didPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) + + /// Tells the delegate that the app successfully registered with Apple Push Notification service (APNs). + /// + /// - Parameter notificationsRegistrationService: The NotificationsRegistrationService object informing the delegate about the registration status. + func notificationsRegistrationServiceDidSuccessfullyRegisterWithAPNs( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol + ) + + /// Tells the delegate when Apple Push Notification service cannot successfully complete the registration process. + /// + /// - Parameters: + /// - notificationsRegistrationService: The NotificationsRegistrationService object informing the delegate about the registration status. + /// - error: An `Error` object that encapsulates information why registration did not succeed. + func notificationsRegistrationServiceDidFailRegisterWithAPNs( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + error: Error + ) +} + +extension NotificationsRegistrationServiceDelegate { + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + shouldPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) -> Bool { + return true + } + + func notificationsRegistrationService( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + didPresentAlertFor alertType: NotificationsRegistrationServiceAlertType + ) { + } + + func notificationsRegistrationServiceDidSuccessfullyRegisterWithAPNs( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol + ) { + } + + func notificationsRegistrationServiceDidFailRegisterWithAPNs( + _ notificationsRegistrationService: NotificationsRegistrationServiceProtocol, + error: Error + ) { + } +} + +// MARK: - NotificationsRegistrationServiceProtocol (NotificationCenter) - + +extension Foundation.Notification.Name { + static let notificationsRegistrationServiceDidUpdatePermissionStatus = Foundation.Notification + .Name("notificationsRegistrationServiceDidUpdatePermissionStatus") +} + +extension NotificationsRegistrationServiceProtocol { + func postCurrentPermissionStatus() { + NotificationPermissionStatus.current.done { status in + NotificationCenter.default.post( + name: .notificationsRegistrationServiceDidUpdatePermissionStatus, + object: status + ) + } + } +} diff --git a/Stepic/SocialAuthPresenter.swift b/Stepic/SocialAuthPresenter.swift index 6870e42e91..e8ad86555e 100644 --- a/Stepic/SocialAuthPresenter.swift +++ b/Stepic/SocialAuthPresenter.swift @@ -92,7 +92,7 @@ class SocialAuthPresenter { AuthInfo.shared.token = token AuthInfo.shared.authorizationType = authorizationType - NotificationRegistrator.shared.registerForRemoteNotificationsIfAlreadyAsked() + NotificationsRegistrationService().renewDeviceToken() return self.stepicsAPI.retrieveCurrentUser() }.then { user -> Promise in @@ -148,7 +148,7 @@ class SocialAuthPresenter { AuthInfo.shared.token = token AuthInfo.shared.authorizationType = authorizationType - NotificationRegistrator.shared.registerForRemoteNotificationsIfAlreadyAsked() + NotificationsRegistrationService().renewDeviceToken() return self.stepicsAPI.retrieveCurrentUser() }.then { user -> Promise in diff --git a/Stepic/StreakNotificationsControlPresenter.swift b/Stepic/StreakNotificationsControlPresenter.swift index d1a4d537a3..fb2f6c123d 100644 --- a/Stepic/StreakNotificationsControlPresenter.swift +++ b/Stepic/StreakNotificationsControlPresenter.swift @@ -10,17 +10,38 @@ import Foundation protocol StreakNotificationsControlView: class { func showStreakTimeSelection(startHour: Int) - func requestNotificationsPermissions() func updateDisplayedStreakTime(startHour: Int) + func setNotificationsSwitchIsOn(_ isOn: Bool) func attachPresenter(_ presenter: StreakNotificationsControlPresenter) } class StreakNotificationsControlPresenter { weak var view: StreakNotificationsControlView? + private let notificationsRegistrationService: NotificationsRegistrationServiceProtocol - init(view: StreakNotificationsControlView) { + init( + view: StreakNotificationsControlView, + notificationsRegistrationService: NotificationsRegistrationServiceProtocol = NotificationsRegistrationService( + presenter: NotificationsRequestOnlySettingsAlertPresenter(context: .streak), + analytics: .init(source: .streakControl) + ) + ) { self.view = view + self.notificationsRegistrationService = notificationsRegistrationService + + NotificationCenter.default.addObserver( + self, + selector: #selector(onPermissionStatusUpdate(_:)), + name: .notificationsRegistrationServiceDidUpdatePermissionStatus, + object: nil + ) + + self.checkPermissionStatus() + } + + deinit { + NotificationCenter.default.removeObserver(self) } private var notificationTimeString: String? { @@ -55,24 +76,53 @@ class StreakNotificationsControlPresenter { } func setStreakNotifications(on allowNotifications: Bool, completion: ((Bool) -> Void)? = nil) { + AnalyticsUserProperties.shared.setStreaksNotificationsEnabled(allowNotifications) + if !allowNotifications { - NotificationsService().cancelStreakLocalNotifications() - PreferencesContainer.notifications.allowStreaksNotifications = false - AnalyticsReporter.reportEvent(AnalyticsEvents.Streaks.preferencesOff, parameters: nil) + self.turnOffNotifications() completion?(false) return } - guard let settings = UIApplication.shared.currentUserNotificationSettings, settings.types != .none else { - view?.requestNotificationsPermissions() + PreferencesContainer.notifications.allowStreaksNotifications = true + self.notificationsRegistrationService.registerForRemoteNotifications() + + guard let settings = UIApplication.shared.currentUserNotificationSettings, settings.types != [] else { completion?(false) return } - PreferencesContainer.notifications.allowStreaksNotifications = true - NotificationRegistrator.shared.registerForRemoteNotifications() - NotificationsService().scheduleStreakLocalNotification(UTCStartHour: PreferencesContainer.notifications.streaksNotificationStartHourUTC) + NotificationsService().scheduleStreakLocalNotification( + UTCStartHour: PreferencesContainer.notifications.streaksNotificationStartHourUTC + ) AnalyticsReporter.reportEvent(AnalyticsEvents.Streaks.preferencesOn, parameters: nil) + completion?(true) } + + private func turnOffNotifications() { + NotificationsService().cancelStreakLocalNotifications() + PreferencesContainer.notifications.allowStreaksNotifications = false + AnalyticsReporter.reportEvent(AnalyticsEvents.Streaks.preferencesOff, parameters: nil) + } + + private func checkPermissionStatus() { + NotificationPermissionStatus.current.done { [weak self] status in + if PreferencesContainer.notifications.allowStreaksNotifications && !status.isRegistered { + self?.turnOffNotifications() + self?.view?.setNotificationsSwitchIsOn(false) + } + } + } + + @objc + private func onPermissionStatusUpdate(_ notification: Foundation.Notification) { + guard let permissionStatus = notification.object as? NotificationPermissionStatus else { + return + } + + self.view?.setNotificationsSwitchIsOn( + PreferencesContainer.notifications.allowStreaksNotifications && permissionStatus.isRegistered + ) + } } diff --git a/Stepic/StreakNotificationsRequestAlertDataSource.swift b/Stepic/StreakNotificationsRequestAlertDataSource.swift new file mode 100644 index 0000000000..46becce43d --- /dev/null +++ b/Stepic/StreakNotificationsRequestAlertDataSource.swift @@ -0,0 +1,61 @@ +// +// StreakNotificationsRequestAlertDataSource.swift +// Stepic +// +// Created by Ivan Magda on 29/10/2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +final class StreakNotificationsRequestAlertDataSource: NotificationsRequestAlertDataSource { + var positiveAction: (() -> Void)? + var negativeAction: (() -> Void)? + + private let streak: Int + + init(streak: Int) { + self.streak = streak + } + + func alert( + for alertType: NotificationsRegistrationServiceAlertType, + in context: NotificationRequestAlertContext + ) -> UIViewController { + switch alertType { + case .permission: + let alert = NotificationRequestAlertViewController(context: context) + alert.currentStreak = self.streak + alert.yesAction = self.positiveAction + alert.noAction = self.negativeAction + + return alert + case .settings: + let alert = UIAlertController( + title: NSLocalizedString("StreakNotificationsAlertTitle", comment: ""), + message: NSLocalizedString("StreakNotificationsAlertMessage", comment: ""), + preferredStyle: .alert + ) + alert.addAction( + UIAlertAction( + title: NSLocalizedString("Yes", comment: ""), + style: .default, + handler: { [weak self] _ in + self?.positiveAction?() + } + ) + ) + alert.addAction( + UIAlertAction( + title: NSLocalizedString("No", comment: ""), + style: .cancel, + handler: { [weak self] _ in + self?.negativeAction?() + } + ) + ) + + return alert + } + } +} diff --git a/Stepic/StreaksAlertManager.swift b/Stepic/StreaksAlertManager.swift deleted file mode 100644 index ff7b080583..0000000000 --- a/Stepic/StreaksAlertManager.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// StreaksAlertManager.swift -// Stepic -// -// Created by Alexander Karpov on 23.11.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import Foundation - -/* - AlertManager class for streaks alert - */ -class StreaksAlertManager: AlertManager { - func present(alert: UIViewController, inController controller: UIViewController) { - controller.present(alert, animated: true, completion: nil) - } - - func construct(notify notifyHandler : @escaping () -> Void) -> UIAlertController { - let alert = UIAlertController(title: "Streaks", message: "Notify about streaks? This option can be changed in preferences.", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "Notify", style: .default, handler: { - _ in - notifyHandler() - })) - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) - return alert - } -} diff --git a/Stepic/StreaksAlertPresentationManager.swift b/Stepic/StreaksAlertPresentationManager.swift index 1bedf8e33e..501bf3b265 100644 --- a/Stepic/StreaksAlertPresentationManager.swift +++ b/Stepic/StreaksAlertPresentationManager.swift @@ -10,40 +10,100 @@ import Foundation import Presentr import PromiseKit -protocol StreaksAlertPresentationDelegate: class { - func didDismiss() -} - -class StreaksAlertPresentationManager { +final class StreaksAlertPresentationManager { weak var controller: UIViewController? - weak var delegate: StreaksAlertPresentationDelegate? + private let source: Source - var source: StreaksAlertPresentationSource? + private var alertPresenter: NotificationsRegistrationPresentationServiceProtocol? private var didTransitionToSettings = false - var notificationPermissionManager: NotificationPermissionManager - enum StreaksAlertPresentationSource: String { - case login = "login" - case submission = "submission" - } + private lazy var notificationsRegistrationService: NotificationsRegistrationServiceProtocol = { + NotificationsRegistrationService(analytics: .init(source: self.source.analyticsSource)) + }() + + private let streakTimePickerPresenter: Presentr = { + let streakTimePickerPresenter = Presentr(presentationType: .popup) + return streakTimePickerPresenter + }() - init(source: StreaksAlertPresentationSource, notificationPermissionManager: NotificationPermissionManager = NotificationPermissionManager()) { + init(source: Source) { self.source = source - self.notificationPermissionManager = notificationPermissionManager - NotificationCenter.default.addObserver(self, selector: #selector(StreaksAlertPresentationManager.becameActive), name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil) + + NotificationCenter.default.addObserver( + self, + selector: #selector(StreaksAlertPresentationManager.becameActive), + name: .UIApplicationWillEnterForeground, + object: nil + ) + } + + deinit { + NotificationCenter.default.removeObserver(self) } - @objc func becameActive() { - if didTransitionToSettings { - didTransitionToSettings = false - cameFromSettings() + func suggestStreak(streak: Int) { + guard let controller = self.controller else { + return + } + + AnalyticsReporter.reportEvent( + AnalyticsEvents.Streaks.notifySuggestionShown( + source: self.source.rawValue, + trigger: RemoteConfig.shared.showStreaksNotificationTrigger.rawValue + ) + ) + + let presenter = NotificationsRequestAlertPresenter( + context: .streak, + dataSource: StreakNotificationsRequestAlertDataSource(streak: streak), + presentAlertIfRegistered: true + ) + let source = self.source.analyticsSource + presenter.onPositiveCallback = { [weak self] in + PreferencesContainer.notifications.allowStreaksNotifications = true + + AnalyticsReporter.reportEvent( + AnalyticsEvents.Streaks.Suggestion.success( + NotificationSuggestionManager().streakAlertShownCnt + ) + ) + NotificationAlertsAnalytics(source: source).reportCustomAlertInteractionResult(.yes) + + // When we are suggesting streak with the `Source` of .login type - `self` will be deallocated at this point. + // In this case we need to register for remote notifications. + if let strongSelf = self { + strongSelf.notifyPressed() + } else { + NotificationsRegistrationService( + analytics: .init(source: source) + ).registerForRemoteNotifications() + } + } + presenter.onCancelCallback = { + PreferencesContainer.notifications.allowStreaksNotifications = false + + AnalyticsReporter.reportEvent( + AnalyticsEvents.Streaks.Suggestion.fail( + NotificationSuggestionManager().streakAlertShownCnt + ) + ) + NotificationAlertsAnalytics(source: source).reportCustomAlertInteractionResult(.no) } + + self.alertPresenter = presenter + self.alertPresenter?.presentAlert(for: .permission, inController: controller) + + NotificationSuggestionManager().didShowAlert(context: .streak) + NotificationAlertsAnalytics(source: source).reportCustomAlertShown() } - private let streakTimePickerPresenter: Presentr = { - let streakTimePickerPresenter = Presentr(presentationType: .popup) - return streakTimePickerPresenter - }() + @objc + private func becameActive() { + if self.didTransitionToSettings { + self.didTransitionToSettings = false + self.cameFromSettings() + } + } private func didChooseTime() { if let controller = controller as? ProfileViewController { @@ -55,87 +115,90 @@ class StreaksAlertPresentationManager { guard let controller = controller else { return } - let vc = NotificationTimePickerViewController(nibName: "PickerViewController", bundle: nil) as NotificationTimePickerViewController + + let vc = NotificationTimePickerViewController( + nibName: "PickerViewController", + bundle: nil + ) as NotificationTimePickerViewController + vc.startHour = PreferencesContainer.notifications.streaksNotificationStartHourLocal - vc.selectedBlock = { - [weak self] in - if let source = self?.source?.rawValue { - AnalyticsReporter.reportEvent(AnalyticsEvents.Streaks.notifySuggestionApproved(source: source, trigger: RemoteConfig.shared.showStreaksNotificationTrigger.rawValue)) + vc.selectedBlock = { [weak self] in + guard let strongSelf = self else { + return } - self?.didChooseTime() - self?.delegate?.didDismiss() + + AnalyticsReporter.reportEvent( + AnalyticsEvents.Streaks.notifySuggestionApproved( + source: strongSelf.source.rawValue, + trigger: RemoteConfig.shared.showStreaksNotificationTrigger.rawValue + ) + ) + + strongSelf.didChooseTime() } vc.cancelAction = { - [weak self] in - self?.delegate?.didDismiss() - } - controller.customPresentViewController(streakTimePickerPresenter, viewController: vc, animated: true, completion: nil) - } - - private func showStreaksSettingsNotificationAlert() { - guard let controller = controller else { - return } - let alert = UIAlertController(title: NSLocalizedString("StreakNotificationsAlertTitle", comment: ""), message: NSLocalizedString("StreakNotificationsAlertMessage", comment: ""), preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("Yes", comment: ""), style: .default, handler: { - [weak self] - _ in - UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!) - self?.didTransitionToSettings = true - })) - alert.addAction(UIAlertAction(title: NSLocalizedString("No", comment: ""), style: .cancel, handler: nil)) - - controller.present(alert, animated: true, completion: nil) + controller.customPresentViewController(streakTimePickerPresenter, viewController: vc, animated: true) } - func notifyPressed() { - notificationPermissionManager.getCurrentPermissionStatus().done { [weak self] status in + private func notifyPressed() { + NotificationPermissionStatus.current.done { [weak self] status in switch status { case .notDetermined: - NotificationRegistrator.shared.registerForRemoteNotifications() + self?.notificationsRegistrationService.registerForRemoteNotifications() self?.selectStreakNotificationTime() case .authorized: self?.selectStreakNotificationTime() case .denied: - self?.showStreaksSettingsNotificationAlert() + self?.showSettingsAlert() } return } } - func cameFromSettings() { - notificationPermissionManager.getCurrentPermissionStatus().done { [weak self] status in + private func showSettingsAlert() { + guard let controller = self.controller, + var alertPresenter = self.alertPresenter else { + return + } + + alertPresenter.onPositiveCallback = { [weak self] in + if let settingsURL = URL(string: UIApplicationOpenSettingsURLString) { + self?.didTransitionToSettings = true + UIApplication.shared.openURL(settingsURL) + } + } + + alertPresenter.presentAlert(for: .settings, inController: controller) + } + + private func cameFromSettings() { + NotificationPermissionStatus.current.done { [weak self] status in switch status { case .notDetermined: - // Actually, it should never come here, but just in case - NotificationRegistrator.shared.registerForRemoteNotifications() + self?.notificationsRegistrationService.registerForRemoteNotifications() case .authorized: self?.selectStreakNotificationTime() case .denied: - //TODO: Add dialog to tell user he should have permitteed the notifications - self?.delegate?.didDismiss() + break } - return } - } - func suggestStreak(streak: Int) { - guard let controller = controller else { - return - } - let alert = Alerts.streaks.construct(presentationManager: self) - - alert.currentStreak = streak + enum Source: String { + case login = "login" + case submission = "submission" - if let source = source?.rawValue { - AnalyticsReporter.reportEvent(AnalyticsEvents.Streaks.notifySuggestionShown(source: source, trigger: RemoteConfig.shared.showStreaksNotificationTrigger.rawValue)) + var analyticsSource: NotificationAlertsAnalytics.Source { + switch self { + case .login: + return .streakAfterLogin + case .submission: + return .streakAfterSubmission( + shownCount: NotificationSuggestionManager().streakAlertShownCnt + ) + } } - Alerts.streaks.present(alert: alert, inController: controller) - } - - deinit { - NotificationCenter.default.removeObserver(self) } } diff --git a/Stepic/StreaksStepikAlertManager.swift b/Stepic/StreaksStepikAlertManager.swift deleted file mode 100644 index fb34f668f4..0000000000 --- a/Stepic/StreaksStepikAlertManager.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// StreaksStepikAlertManager.swift -// Stepic -// -// Created by Alexander Karpov on 08.12.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import Foundation -import Presentr - -/* - AlertManager class for streaks alert - */ -class StreaksStepikAlertManager: AlertManager, StreaksAlertPresentationDelegate { - func present(alert: UIViewController, inController controller: UIViewController) { - controller.customPresentViewController(presenter, viewController: alert, animated: true, completion: nil) -// controller.present(alert, animated: true, completion: nil) - } - - //TODO: Add DI here - var presentationManager: StreaksAlertPresentationManager? - var streaksNotificationSuggestionManager = NotificationSuggestionManager() - - let presenter: Presentr = { - let presenter = Presentr(presentationType: .dynamic(center: .center)) - presenter.roundCorners = true - return presenter - }() - - func construct(presentationManager: StreaksAlertPresentationManager) -> NotificationRequestAlertViewController { - self.presentationManager = presentationManager - presentationManager.delegate = self - let alert = NotificationRequestAlertViewController(nibName: "NotificationRequestAlertViewController", bundle: nil) - alert.context = .streak - alert.yesAction = { - [weak self] in - PreferencesContainer.notifications.allowStreaksNotifications = true - - guard let strongSelf = self else { - return - } - AnalyticsReporter.reportEvent(AnalyticsEvents.Streaks.Suggestion.success(strongSelf.streaksNotificationSuggestionManager.streakAlertShownCnt)) - strongSelf.presentationManager?.notifyPressed() - } - alert.noAction = { - [weak self] in - PreferencesContainer.notifications.allowStreaksNotifications = false - - guard let strongSelf = self else { - return - } - AnalyticsReporter.reportEvent(AnalyticsEvents.Streaks.Suggestion.fail(strongSelf.streaksNotificationSuggestionManager.streakAlertShownCnt)) - strongSelf.presentationManager = nil - } - return alert - } - - func didDismiss() { - self.presentationManager = nil - } -} diff --git a/Stepic/Comment.swift b/Stepic/Utils/Discussions/Comment.swift similarity index 100% rename from Stepic/Comment.swift rename to Stepic/Utils/Discussions/Comment.swift diff --git a/Stepic/DiscussionProxy.swift b/Stepic/Utils/Discussions/DiscussionProxy.swift similarity index 100% rename from Stepic/DiscussionProxy.swift rename to Stepic/Utils/Discussions/DiscussionProxy.swift diff --git a/Stepic/Vote.swift b/Stepic/Utils/Discussions/Vote.swift similarity index 100% rename from Stepic/Vote.swift rename to Stepic/Utils/Discussions/Vote.swift diff --git a/Stepic/HTMLParsingUtil.swift b/Stepic/Utils/HTMLParsingUtil.swift similarity index 100% rename from Stepic/HTMLParsingUtil.swift rename to Stepic/Utils/HTMLParsingUtil.swift diff --git a/Stepic/TagDetectionUtil.swift b/Stepic/Utils/TagDetectionUtil.swift similarity index 100% rename from Stepic/TagDetectionUtil.swift rename to Stepic/Utils/TagDetectionUtil.swift diff --git a/Stepic/UIImageView+SVGDownload.swift b/Stepic/Utils/UIImageView+SVGDownload.swift similarity index 100% rename from Stepic/UIImageView+SVGDownload.swift rename to Stepic/Utils/UIImageView+SVGDownload.swift diff --git a/Stepic/en.lproj/Localizable.strings b/Stepic/en.lproj/Localizable.strings index 47c1f5883e..b8c4b71bee 100644 --- a/Stepic/en.lproj/Localizable.strings +++ b/Stepic/en.lproj/Localizable.strings @@ -168,6 +168,10 @@ NotificationTime = "Notification time"; Notifications = "Notifications"; StreakNotificationsAlertTitle = "Streak notifications"; StreakNotificationsAlertMessage = "Seems like you've disabled notifications in system settings. Enable them in preferences?"; +DeniedNotificationsDefaultAlertTitle = "Notifications are turned off"; +DeniedNotificationsDefaultAlertMessage = "Seems like you've disabled notifications. Turn on in settings?"; +NotificationRequestDefaultAlertTitle = "Stay tuned"; +NotificationRequestDefaultAlertMessage = "We are trying to make learning process effective and comfortable. Notify about interesting and relevant content to you?"; PassedPercent = "Passed"; AuthorDidntUploadVideo = "Author didn't upload any video yet."; NoVideo = "No video"; @@ -326,6 +330,9 @@ CatalogPlaceholderError = "Could not load catalog. Press to retry."; TrendingTopics = "Trending topics"; WidgetButtonJoin = "Join"; +/* a/b */ +WidgetButtonJoinTest = "Learn"; + WidgetButtonInfo = "Info"; WidgetButtonSyllabus = "Syllabus"; WidgetButtonLearn = "Learn"; diff --git a/Stepic/ru.lproj/Localizable.strings b/Stepic/ru.lproj/Localizable.strings index 4762eb1657..73a2894963 100644 --- a/Stepic/ru.lproj/Localizable.strings +++ b/Stepic/ru.lproj/Localizable.strings @@ -168,6 +168,10 @@ NotificationTime = "Время напоминания"; Notifications = "Уведомления"; StreakNotificationsAlertTitle = "Уведомления"; StreakNotificationsAlertMessage = "Похоже, вы запретили приложению присылать уведомления. Разрешить в настройках системы?"; +DeniedNotificationsDefaultAlertTitle = "Уведомления отключены"; +DeniedNotificationsDefaultAlertMessage = "Похоже, вы отключили уведомления. Включить в настройках?"; +NotificationRequestDefaultAlertTitle = "Следи за обновлениями"; +NotificationRequestDefaultAlertMessage = "Мы пытаемся сделать процесс обучения максимально удобным и полезным. Получать уведомления об интересном и актуальном для вас контенте?"; PassedPercent = "Пройдено"; AuthorDidntUploadVideo = "Автор еще не загрузил видео."; NoVideo = "Нет видео"; @@ -327,6 +331,9 @@ CatalogPlaceholderError = "Не удалось загрузить каталог TrendingTopics = "Категории"; WidgetButtonJoin = "Поступить"; +/* a/b */ +WidgetButtonJoinTest = "Учиться"; + WidgetButtonInfo = "Инфо"; WidgetButtonSyllabus = "План"; WidgetButtonLearn = "Учиться"; diff --git a/StepicAdaptiveCourse/Content/1838/AdaptiveInfo.plist b/StepicAdaptiveCourse/Content/1838/AdaptiveInfo.plist index ff8726a20c..5a9c6d2760 100644 --- a/StepicAdaptiveCourse/Content/1838/AdaptiveInfo.plist +++ b/StepicAdaptiveCourse/Content/1838/AdaptiveInfo.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.70 + 1.71 CFBundleURLTypes @@ -44,7 +44,7 @@ CFBundleVersion - 107 + 109 Fabric APIKey diff --git a/StepicAdaptiveCourse/Content/3124/AdaptiveInfo.plist b/StepicAdaptiveCourse/Content/3124/AdaptiveInfo.plist index 4fcab49643..47cd2dfabc 100644 --- a/StepicAdaptiveCourse/Content/3124/AdaptiveInfo.plist +++ b/StepicAdaptiveCourse/Content/3124/AdaptiveInfo.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.70 + 1.71 CFBundleURLTypes @@ -44,7 +44,7 @@ CFBundleVersion - 107 + 109 Fabric APIKey diff --git a/StepicAdaptiveCourse/Content/3149/AdaptiveInfo.plist b/StepicAdaptiveCourse/Content/3149/AdaptiveInfo.plist index 1a89e72515..5c504a75a5 100644 --- a/StepicAdaptiveCourse/Content/3149/AdaptiveInfo.plist +++ b/StepicAdaptiveCourse/Content/3149/AdaptiveInfo.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.70 + 1.71 CFBundleURLTypes @@ -44,7 +44,7 @@ CFBundleVersion - 107 + 109 Fabric APIKey diff --git a/StepicAdaptiveCourse/Content/3150/AdaptiveInfo.plist b/StepicAdaptiveCourse/Content/3150/AdaptiveInfo.plist index 6797a7a4d9..37ef1f0588 100644 --- a/StepicAdaptiveCourse/Content/3150/AdaptiveInfo.plist +++ b/StepicAdaptiveCourse/Content/3150/AdaptiveInfo.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.70 + 1.71 CFBundleURLTypes @@ -44,7 +44,7 @@ CFBundleVersion - 107 + 109 Fabric APIKey diff --git a/StepicTests/Info.plist b/StepicTests/Info.plist index 517dff5949..5b8d4f224b 100644 --- a/StepicTests/Info.plist +++ b/StepicTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.70 + 1.71 CFBundleSignature ???? CFBundleVersion - 107 + 109 diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json index 9be9adbf7d..aefef2914e 100644 --- a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json @@ -2,13 +2,23 @@ "images" : [ { "idiom" : "watch", - "screenWidth" : "{130,145}", - "scale" : "2x" + "scale" : "2x", + "screen-width" : "<=145" }, { "idiom" : "watch", - "screenWidth" : "{146,165}", - "scale" : "2x" + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Contents.json index 2eca9a1f46..e0b0ff34ac 100644 --- a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Contents.json +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Contents.json @@ -10,6 +10,26 @@ "filename" : "Extra Large.imageset", "role" : "extra-large" }, + { + "idiom" : "watch", + "filename" : "Graphic Bezel-4.imageset", + "role" : "graphic-bezel" + }, + { + "idiom" : "watch", + "filename" : "Graphic Circular-4.imageset", + "role" : "graphic-circular" + }, + { + "idiom" : "watch", + "filename" : "Graphic Corner-4.imageset", + "role" : "graphic-corner" + }, + { + "idiom" : "watch", + "filename" : "Graphic Large Rectangular-4.imageset", + "role" : "graphic-large-rectangular" + }, { "idiom" : "watch", "filename" : "Modular.imageset", @@ -25,4 +45,4 @@ "version" : 1, "author" : "xcode" } -} +} \ No newline at end of file diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json index 9be9adbf7d..aefef2914e 100644 --- a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json @@ -2,13 +2,23 @@ "images" : [ { "idiom" : "watch", - "screenWidth" : "{130,145}", - "scale" : "2x" + "scale" : "2x", + "screen-width" : "<=145" }, { "idiom" : "watch", - "screenWidth" : "{146,165}", - "scale" : "2x" + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel-4.imageset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel-4.imageset/Contents.json new file mode 100644 index 0000000000..e011e32711 --- /dev/null +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Bezel-4.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular-4.imageset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular-4.imageset/Contents.json new file mode 100644 index 0000000000..e011e32711 --- /dev/null +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Circular-4.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner-4.imageset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner-4.imageset/Contents.json new file mode 100644 index 0000000000..e011e32711 --- /dev/null +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Corner-4.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular-4.imageset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular-4.imageset/Contents.json new file mode 100644 index 0000000000..e011e32711 --- /dev/null +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular-4.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "watch", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json index 9be9adbf7d..aefef2914e 100644 --- a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json @@ -2,13 +2,23 @@ "images" : [ { "idiom" : "watch", - "screenWidth" : "{130,145}", - "scale" : "2x" + "scale" : "2x", + "screen-width" : "<=145" }, { "idiom" : "watch", - "screenWidth" : "{146,165}", - "scale" : "2x" + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json index 9be9adbf7d..aefef2914e 100644 --- a/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json +++ b/StepicWatch Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json @@ -2,13 +2,23 @@ "images" : [ { "idiom" : "watch", - "screenWidth" : "{130,145}", - "scale" : "2x" + "scale" : "2x", + "screen-width" : "<=145" }, { "idiom" : "watch", - "screenWidth" : "{146,165}", - "scale" : "2x" + "scale" : "2x", + "screen-width" : ">161" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">145" + }, + { + "idiom" : "watch", + "scale" : "2x", + "screen-width" : ">183" } ], "info" : { diff --git a/StepicWatch Extension/Info.plist b/StepicWatch Extension/Info.plist index c86de2de5e..2a8624cd09 100644 --- a/StepicWatch Extension/Info.plist +++ b/StepicWatch Extension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.70 + 1.71 CFBundleVersion - 107 + 109 CLKComplicationPrincipalClass $(PRODUCT_MODULE_NAME).ComplicationController CLKComplicationSupportedFamilies diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Contents.json b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Contents.json index 75bffd15cc..d177487671 100644 --- a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -3,7 +3,7 @@ { "size" : "24x24", "idiom" : "watch", - "filename" : "rsz_icon-603x.png", + "filename" : "Icon-48.png", "scale" : "2x", "role" : "notificationCenter", "subtype" : "38mm" @@ -11,7 +11,7 @@ { "size" : "27.5x27.5", "idiom" : "watch", - "filename" : "rsz_1icon-603x.png", + "filename" : "Icon-55.png", "scale" : "2x", "role" : "notificationCenter", "subtype" : "42mm" @@ -19,29 +19,45 @@ { "size" : "29x29", "idiom" : "watch", - "filename" : "Icon-29@2x.png", + "filename" : "Icon-58.png", "role" : "companionSettings", "scale" : "2x" }, { "size" : "29x29", "idiom" : "watch", - "filename" : "Icon-29@3x.png", + "filename" : "Icon-87.png", "role" : "companionSettings", "scale" : "3x" }, { "size" : "40x40", "idiom" : "watch", - "filename" : "Icon-40@2x.png", + "filename" : "Icon-80.png", "scale" : "2x", "role" : "appLauncher", "subtype" : "38mm" }, + { + "size" : "44x44", + "idiom" : "watch", + "filename" : "Icon-88.png", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "40mm" + }, + { + "size" : "50x50", + "idiom" : "watch", + "filename" : "Icon-100.png", + "scale" : "2x", + "role" : "appLauncher", + "subtype" : "44mm" + }, { "size" : "86x86", "idiom" : "watch", - "filename" : "rsz_icon-1024.png", + "filename" : "Icon-172.png", "scale" : "2x", "role" : "quickLook", "subtype" : "38mm" @@ -49,15 +65,23 @@ { "size" : "98x98", "idiom" : "watch", - "filename" : "rsz_1icon-1024.png", + "filename" : "Icon-196.png", "scale" : "2x", "role" : "quickLook", "subtype" : "42mm" }, + { + "size" : "108x108", + "idiom" : "watch", + "filename" : "Icon-216.png", + "scale" : "2x", + "role" : "quickLook", + "subtype" : "44mm" + }, { "size" : "1024x1024", "idiom" : "watch-marketing", - "filename" : "iTunesWatchArtwork.png", + "filename" : "icon-b-black.png", "scale" : "1x" } ], diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-100.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-100.png new file mode 100644 index 0000000000..de8f91f332 Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-100.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-172.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-172.png new file mode 100644 index 0000000000..64fb90ad64 Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-172.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-196.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-196.png new file mode 100644 index 0000000000..dac270407f Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-196.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-216.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-216.png new file mode 100644 index 0000000000..ff0a3caa51 Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-216.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png deleted file mode 100644 index 3a475f243b..0000000000 Binary files a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png deleted file mode 100644 index 6752071549..0000000000 Binary files a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png deleted file mode 100644 index bd513e26a0..0000000000 Binary files a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-48.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-48.png new file mode 100644 index 0000000000..461fda10db Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-48.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-55.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-55.png new file mode 100644 index 0000000000..56fd995902 Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-55.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-58.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-58.png new file mode 100644 index 0000000000..5d431e4e7f Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-58.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-80.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-80.png new file mode 100644 index 0000000000..cbce7fd409 Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-80.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-87.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-87.png new file mode 100644 index 0000000000..979f4227b2 Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-87.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-88.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-88.png new file mode 100644 index 0000000000..0b93ed79b9 Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/Icon-88.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/iTunesWatchArtwork.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/iTunesWatchArtwork.png deleted file mode 100644 index 63457efd0b..0000000000 Binary files a/StepicWatch/Assets.xcassets/AppIcon.appiconset/iTunesWatchArtwork.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/icon-b-black.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/icon-b-black.png new file mode 100644 index 0000000000..ece1fcdbee Binary files /dev/null and b/StepicWatch/Assets.xcassets/AppIcon.appiconset/icon-b-black.png differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_1icon-1024.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_1icon-1024.png deleted file mode 100644 index fb0f2ff995..0000000000 Binary files a/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_1icon-1024.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_1icon-603x.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_1icon-603x.png deleted file mode 100644 index ae7ad588c8..0000000000 Binary files a/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_1icon-603x.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_icon-1024.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_icon-1024.png deleted file mode 100644 index 58fb23399a..0000000000 Binary files a/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_icon-1024.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_icon-603x.png b/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_icon-603x.png deleted file mode 100644 index fa26ec32df..0000000000 Binary files a/StepicWatch/Assets.xcassets/AppIcon.appiconset/rsz_icon-603x.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/Icon-40.imageset/Contents.json b/StepicWatch/Assets.xcassets/Icon-40.imageset/Contents.json index 681aa1c19a..6aa0511836 100644 --- a/StepicWatch/Assets.xcassets/Icon-40.imageset/Contents.json +++ b/StepicWatch/Assets.xcassets/Icon-40.imageset/Contents.json @@ -6,7 +6,7 @@ }, { "idiom" : "universal", - "filename" : "Icon-40@2x.png", + "filename" : "Icon-80.png", "scale" : "2x" }, { diff --git a/StepicWatch/Assets.xcassets/Icon-40.imageset/Icon-40@2x.png b/StepicWatch/Assets.xcassets/Icon-40.imageset/Icon-40@2x.png deleted file mode 100644 index bd513e26a0..0000000000 Binary files a/StepicWatch/Assets.xcassets/Icon-40.imageset/Icon-40@2x.png and /dev/null differ diff --git a/StepicWatch/Assets.xcassets/Icon-40.imageset/Icon-80.png b/StepicWatch/Assets.xcassets/Icon-40.imageset/Icon-80.png new file mode 100644 index 0000000000..cbce7fd409 Binary files /dev/null and b/StepicWatch/Assets.xcassets/Icon-40.imageset/Icon-80.png differ diff --git a/StepicWatch/Info.plist b/StepicWatch/Info.plist index 1f536af282..d9cf8dcb31 100644 --- a/StepicWatch/Info.plist +++ b/StepicWatch/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.70 + 1.71 CFBundleVersion - 107 + 109 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/StepikTV/Info.plist b/StepikTV/Info.plist index 31cdbacd58..77399ec2fa 100644 --- a/StepikTV/Info.plist +++ b/StepikTV/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.70 + 1.71 CFBundleVersion - 107 + 109 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/StepikTVTests/Info.plist b/StepikTVTests/Info.plist index 58541d0e12..3d4101e6f7 100644 --- a/StepikTVTests/Info.plist +++ b/StepikTVTests/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.70 + 1.71 CFBundleVersion - 107 + 109 diff --git a/StickerPackExtension/Info.plist b/StickerPackExtension/Info.plist index 9ed48e397b..11900a1e81 100644 --- a/StickerPackExtension/Info.plist +++ b/StickerPackExtension/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.70 + 1.71 CFBundleVersion - 107 + 109 NSExtension NSExtensionPointIdentifier diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-27x20@2x.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-27x20@2x.png index 964a400a82..bbbf977460 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-27x20@2x.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-27x20@2x.png differ diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-27x20@3x.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-27x20@3x.png index 9f18a6f467..18f5c6da00 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-27x20@3x.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-27x20@3x.png differ diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPadAir-67x50@2x.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPadAir-67x50@2x.png index 254ddbab8b..c9e4ff0962 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPadAir-67x50@2x.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPadAir-67x50@2x.png differ diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPadAir-74x55@2x.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPadAir-74x55@2x.png index d96d0ec6dc..824a63aa8c 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPadAir-74x55@2x.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPadAir-74x55@2x.png differ diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPhone-60x45@2x.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPhone-60x45@2x.png index 9889daf7bd..d8982e055d 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPhone-60x45@2x.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPhone-60x45@2x.png differ diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPhone-60x45@3x.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPhone-60x45@3x.png index 740c694d93..7f69d8fd63 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPhone-60x45@3x.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-iPhone-60x45@3x.png differ diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-store-1024x768.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-store-1024x768.png index f15ca7dbba..33ae988fc2 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-store-1024x768.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-app-store-1024x768.png differ diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-transcript-32x24@2x.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-transcript-32x24@2x.png index a095a0bb92..18bd49ab5f 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-transcript-32x24@2x.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-transcript-32x24@2x.png differ diff --git a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-transcript-32x24@3x.png b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-transcript-32x24@3x.png index 826c2c4e08..9762894083 100644 Binary files a/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-transcript-32x24@3x.png and b/StickerPackExtension/Stickers.xcassets/iMessage App Icon.stickersiconset/icon-messages-transcript-32x24@3x.png differ diff --git a/UITests/Screenshots/Info.plist b/UITests/Screenshots/Info.plist index 97699daa56..de8957cac5 100644 --- a/UITests/Screenshots/Info.plist +++ b/UITests/Screenshots/Info.plist @@ -15,8 +15,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.70 + 1.71 CFBundleVersion - 107 + 109 diff --git a/fastlane/metadata/Stepic/app_icon.jpg b/fastlane/metadata/Stepic/app_icon.jpg index eb53dfeba0..7864658fbf 100644 Binary files a/fastlane/metadata/Stepic/app_icon.jpg and b/fastlane/metadata/Stepic/app_icon.jpg differ diff --git a/fastlane/metadata/Stepic/copyright.txt b/fastlane/metadata/Stepic/copyright.txt index 6b16e82865..d961f984e3 100644 --- a/fastlane/metadata/Stepic/copyright.txt +++ b/fastlane/metadata/Stepic/copyright.txt @@ -1 +1 @@ -2015 Stepik +2018 Stepik diff --git a/fastlane/metadata/Stepic/en-GB/description.txt b/fastlane/metadata/Stepic/en-GB/description.txt new file mode 100644 index 0000000000..105da521b8 --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/description.txt @@ -0,0 +1,24 @@ +Stepik is a platform with free online courses. Now also mobile! +Courses contain video lectures and practical assignments, which you can learn at any time. +With this app, you can also download all the content to watch offline or on the road. + +There are many popular online courses from leading IT companies and universities on Stepik: +• Computer Science Center +• Yandex Academy +• Mail.Ru Group +• Academic University of RAS +• European University in St. Petersburg +• Bioinformatics Institute +• Higher School of Economics +• MIPT, MISiS +... and other educational organizations, companies and instructors. + +On Stepik you will find courses on: +• Computer Science and Technology +• Mathematics +• Biology and Bioinformatics +• Economics +• Psychology +... and other disciplines. + +Learn the new! For free. diff --git a/fastlane/metadata/Stepic/en-GB/keywords.txt b/fastlane/metadata/Stepic/en-GB/keywords.txt new file mode 100644 index 0000000000..d8a6d45466 --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/keywords.txt @@ -0,0 +1,2 @@ +code,learn,programming,python,java,cpp,kotlin,edu,online,toefl,mooc,data science,courses + diff --git a/fastlane/metadata/Stepic/en-GB/marketing_url.txt b/fastlane/metadata/Stepic/en-GB/marketing_url.txt new file mode 100644 index 0000000000..989f18fcc2 --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/marketing_url.txt @@ -0,0 +1 @@ +http://stepik.org diff --git a/fastlane/metadata/Stepic/en-GB/name.txt b/fastlane/metadata/Stepic/en-GB/name.txt new file mode 100644 index 0000000000..f2ff65696d --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/name.txt @@ -0,0 +1 @@ +Stepik: best online courses diff --git a/fastlane/metadata/Stepic/en-GB/privacy_url.txt b/fastlane/metadata/Stepic/en-GB/privacy_url.txt new file mode 100644 index 0000000000..9fcd2ad9bd --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/privacy_url.txt @@ -0,0 +1 @@ +https://stepik.org/page/privacy diff --git a/fastlane/metadata/Stepic/en-GB/promotional_text.txt b/fastlane/metadata/Stepic/en-GB/promotional_text.txt new file mode 100644 index 0000000000..69dd44f0f2 --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/promotional_text.txt @@ -0,0 +1 @@ +With Stepik iOS app you can watch videos, read lectures and solve different quizzes for free. Learning programming? Just try our code editor! diff --git a/fastlane/metadata/Stepic/en-GB/release_notes.txt b/fastlane/metadata/Stepic/en-GB/release_notes.txt new file mode 100644 index 0000000000..7317e63b71 --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/release_notes.txt @@ -0,0 +1,4 @@ +In the new version of Stepik we fixed some bugs and updated app icon. + +Enjoy learning! +Stepik team \ No newline at end of file diff --git a/fastlane/metadata/Stepic/en-GB/subtitle.txt b/fastlane/metadata/Stepic/en-GB/subtitle.txt new file mode 100644 index 0000000000..138363e34a --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/subtitle.txt @@ -0,0 +1 @@ +Learn online! diff --git a/fastlane/metadata/Stepic/en-GB/support_url.txt b/fastlane/metadata/Stepic/en-GB/support_url.txt new file mode 100644 index 0000000000..79451d8e75 --- /dev/null +++ b/fastlane/metadata/Stepic/en-GB/support_url.txt @@ -0,0 +1 @@ +http://stepik.help diff --git a/fastlane/metadata/Stepic/en-US/release_notes.txt b/fastlane/metadata/Stepic/en-US/release_notes.txt index 2c68c0b1ac..7317e63b71 100644 --- a/fastlane/metadata/Stepic/en-US/release_notes.txt +++ b/fastlane/metadata/Stepic/en-US/release_notes.txt @@ -1,4 +1,4 @@ -In the new version of Stepik we fixed landscape orientation on iPhones and improved app stability. +In the new version of Stepik we fixed some bugs and updated app icon. Enjoy learning! -Stepik team +Stepik team \ No newline at end of file diff --git a/fastlane/metadata/Stepic/ru/release_notes.txt b/fastlane/metadata/Stepic/ru/release_notes.txt index d90b3b3a28..9bf33addbb 100644 --- a/fastlane/metadata/Stepic/ru/release_notes.txt +++ b/fastlane/metadata/Stepic/ru/release_notes.txt @@ -1,4 +1,4 @@ -В новой версии Stepik мы исправили краш приложения при переходе к последнему уроку, поправили ландшафтную ориентацию на экране каталога и повысили стабильность приложения. +В новой версии Stepik мы сделали приложение более стабильным. А ещё обновили иконку. Учитесь в удовольствие! Команда Stepik diff --git a/fastlane/metadata/Stepic/watch_icon.jpg b/fastlane/metadata/Stepic/watch_icon.jpg index eb53dfeba0..7864658fbf 100644 Binary files a/fastlane/metadata/Stepic/watch_icon.jpg and b/fastlane/metadata/Stepic/watch_icon.jpg differ