diff --git a/Podfile b/Podfile index b2e1494414..1de00c0bf5 100644 --- a/Podfile +++ b/Podfile @@ -61,7 +61,7 @@ def all_pods pod 'Charts', '3.0.4' pod 'EasyTipView', :git => 'https://github.com/igorkislyuk/EasyTipView.git' pod 'ActionSheetPicker-3.0' - pod 'SkeletonView' + pod 'NotificationBannerSwift' end def testing_pods @@ -69,13 +69,8 @@ def testing_pods pod 'Nimble' end -def main_pods - pod 'Appsee' -end - def adaptive_pods pod 'SDWebImage/GIF' - pod 'NotificationBannerSwift', '1.5.2' end target 'Stepic' do @@ -84,7 +79,6 @@ target 'Stepic' do target 'StepicTests' do inherit! :search_paths all_pods - main_pods testing_pods end end diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index 462e1fa66f..85224a8dd1 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -1888,6 +1888,15 @@ 2C1B64E01F4C596500236804 /* Config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C1B64CE1F4C595F00236804 /* Config.plist */; }; 2C1B64E31F4C597F00236804 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 2C1B64D31F4C595F00236804 /* icon.png */; }; 2C1B64E81F4C5A9E00236804 /* Icons.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C1B64E61F4C5A6D00236804 /* Icons.xcassets */; }; + 2C22042520E277B40060117A /* Skeletonable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22042420E277B40060117A /* Skeletonable.swift */; }; + 2C22042720E277E50060117A /* SkeletonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22042620E277E50060117A /* SkeletonView.swift */; }; + 2C22042B20E277F70060117A /* SkeletonViewAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22042820E277F60060117A /* SkeletonViewAnimation.swift */; }; + 2C22042C20E277F70060117A /* Skeletonable+UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22042920E277F60060117A /* Skeletonable+UIView.swift */; }; + 2C22042D20E277F70060117A /* Skeletonable+UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C22042A20E277F60060117A /* Skeletonable+UITableView.swift */; }; + 2C22043020E27E400060117A /* ProfileCellSkeletonPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C22042F20E27E400060117A /* ProfileCellSkeletonPlaceholderView.xib */; }; + 2C22043220E2804F0060117A /* AchievementSkeletonPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C22043120E2804F0060117A /* AchievementSkeletonPlaceholderView.xib */; }; + 2C22043420E28AD50060117A /* AchievementListSkeletonPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C22043320E28AD50060117A /* AchievementListSkeletonPlaceholderView.xib */; }; + 2C22043620E2A0860060117A /* ProfileAchievementsContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C22043520E2A0860060117A /* ProfileAchievementsContentView.xib */; }; 2C23C5DE1F6AB10800FC2B7C /* SocialAuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C23C5DD1F6AB10800FC2B7C /* SocialAuthPresenter.swift */; }; 2C23C5E01F6AB2AD00FC2B7C /* SocialAuthProviders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C23C5DF1F6AB2AD00FC2B7C /* SocialAuthProviders.swift */; }; 2C23C5E21F6BD43300FC2B7C /* EmailAuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C23C5E11F6BD43300FC2B7C /* EmailAuthPresenter.swift */; }; @@ -2199,6 +2208,10 @@ 2C6A76552045CF4600C509A6 /* MigrationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0AF18B203C67EC000EA3B6 /* MigrationExtensions.swift */; }; 2C6A76562045CF5C00C509A6 /* DefaultsStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C97040C1F3A0DCF00C36F0A /* DefaultsStorageManager.swift */; }; 2C6A76572045CFDD00C509A6 /* UICollectionViewFlowLayout+PlusCrashWorkaround.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C15EB951FC70A0300F56D93 /* UICollectionViewFlowLayout+PlusCrashWorkaround.swift */; }; + 2C6B2F9C20D7F24800F7C976 /* AchievementPopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6B2F9A20D7F24800F7C976 /* AchievementPopupViewController.swift */; }; + 2C6B2F9D20D7F24800F7C976 /* AchievementPopupViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C6B2F9B20D7F24800F7C976 /* AchievementPopupViewController.xib */; }; + 2C6B2FA120D95B2800F7C976 /* AchievementNotificationBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6B2F9F20D95B2800F7C976 /* AchievementNotificationBannerView.swift */; }; + 2C6B2FA220D95B2800F7C976 /* AchievementNotificationBannerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C6B2FA020D95B2800F7C976 /* AchievementNotificationBannerView.xib */; }; 2C6E9CD41FED657E001821A2 /* Adaptive.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C6E9CD31FED657E001821A2 /* Adaptive.storyboard */; }; 2C6E9CD51FED6ADD001821A2 /* loading_robot.gif in Resources */ = {isa = PBXBuildFile; fileRef = 863262F81ECFC5AE007A20B3 /* loading_robot.gif */; }; 2C6E9CDB1FF27543001821A2 /* AdaptiveStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6E9CDA1FF27543001821A2 /* AdaptiveStorageManager.swift */; }; @@ -3675,6 +3688,8 @@ 2CB9E8D11F839C8E0004E17F /* NotificationsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CB9E8CF1F839C8E0004E17F /* NotificationsTableViewCell.xib */; }; 2CBC4C061F1E4A1300FE96C4 /* Config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2CBC4C051F1E4A1300FE96C4 /* Config.plist */; }; 2CBC4C091F1E548900FE96C4 /* StepicApplicationsInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D649B1C172015003222F0 /* StepicApplicationsInfo.swift */; }; + 2CBCBD4A20D1AAFC000B5732 /* AchievementsListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBCBD4820D1AAFC000B5732 /* AchievementsListTableViewCell.swift */; }; + 2CBCBD4B20D1AAFC000B5732 /* AchievementsListTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CBCBD4920D1AAFC000B5732 /* AchievementsListTableViewCell.xib */; }; 2CBD855C201799B700E14F83 /* AdaptiveRatingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CBD855B201799B700E14F83 /* AdaptiveRatingsViewController.swift */; }; 2CC0754720177A2E004A6005 /* AdaptiveStatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC0754620177A2E004A6005 /* AdaptiveStatsViewController.swift */; }; 2CC351861F6827BE004255B6 /* Auth.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2CC351841F6827BE004255B6 /* Auth.storyboard */; }; @@ -3724,6 +3739,12 @@ 2CE527C22029D9030047EC5F /* AdaptiveStatsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE527C02029D8EE0047EC5F /* AdaptiveStatsSection.swift */; }; 2CE527C72029DFE70047EC5F /* AdaptiveAdaptiveStatsPagerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE527C42029D9780047EC5F /* AdaptiveAdaptiveStatsPagerViewController.swift */; }; 2CE527C82029E1600047EC5F /* AdaptiveAchievementsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1649661F2A0842002C9F99 /* AdaptiveAchievementsViewController.swift */; }; + 2CE8390820C7F16700FE3672 /* ContentMenuBlockTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE8390620C7F16700FE3672 /* ContentMenuBlockTableViewCell.swift */; }; + 2CE8390920C7F16700FE3672 /* ContentMenuBlockTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CE8390720C7F16700FE3672 /* ContentMenuBlockTableViewCell.xib */; }; + 2CE8390C20C8094000FE3672 /* ProfileAchievementsContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE8390B20C8094000FE3672 /* ProfileAchievementsContentView.swift */; }; + 2CE8390E20C8096400FE3672 /* ProfileAchievementsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE8390D20C8096400FE3672 /* ProfileAchievementsPresenter.swift */; }; + 2CE8391120C8101500FE3672 /* AchievementBadgeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CE8391020C8101500FE3672 /* AchievementBadgeView.xib */; }; + 2CE8391320C8102300FE3672 /* AchievementBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CE8391220C8102200FE3672 /* AchievementBadgeView.swift */; }; 2CEDA3601F336FEC005F4A5D /* AdaptiveRatingHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CEDA35F1F336FEC005F4A5D /* AdaptiveRatingHelper.swift */; }; 2CF0885A205BEBF500FCB9C0 /* StepikTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CF08859205BEBF500FCB9C0 /* StepikTableView.swift */; }; 2CF0885D205BED9700FCB9C0 /* StepikPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CF0885C205BED9700FCB9C0 /* StepikPlaceholderView.xib */; }; @@ -3762,6 +3783,7 @@ 62E9810B72ACC505856D8329 /* PinsMapPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98750EE80BFD97E4C7072 /* PinsMapPresenter.swift */; }; 62E9813EA4EF290CAD112E87 /* ProfileHeaderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98FC406CB025F46041481 /* ProfileHeaderInfoView.swift */; }; 62E9815A6AAC6C12E9B9D862 /* ProfileHeaderInfoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62E98801330B94B34FA0D7B9 /* ProfileHeaderInfoView.xib */; }; + 62E98176284407D3D2A0E271 /* Achievement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98708CFECD3F1D0AD4120 /* Achievement.swift */; }; 62E981858E2E696693A1AB87 /* PinsMapBlockContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E985F73C3288F8D0045327 /* PinsMapBlockContentView.swift */; }; 62E981AC996C4AA91527B064 /* WrappingNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98E55BD59203DE01307B1 /* WrappingNavigationViewController.swift */; }; 62E981B26AD9DC231C5414D1 /* PinsMapBlockContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E985F73C3288F8D0045327 /* PinsMapBlockContentView.swift */; }; @@ -3774,6 +3796,7 @@ 62E9831EA7A75D89A3C9EC5B /* CourseListViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9865A5FE3936F4ADA7133 /* CourseListViewData.swift */; }; 62E983397736699787D8DD35 /* UIView+fromNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E982BB5246C2BBC5E88D06 /* UIView+fromNib.swift */; }; 62E9834EF86EA5676FD4686E /* ProfileInfoPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98275C20698D3770CD6D8 /* ProfileInfoPresenter.swift */; }; + 62E98381EC6AD8DD7613C32D /* AchievementsListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E983ECB91B2F753DC02E22 /* AchievementsListViewController.swift */; }; 62E9839060309CC98517D0FB /* ProfileDescriptionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98AFF3B8AC3CB8374445B /* ProfileDescriptionPresenter.swift */; }; 62E983B11CD5947FF234C550 /* ProfileHeaderInfoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62E98801330B94B34FA0D7B9 /* ProfileHeaderInfoView.xib */; }; 62E983B6D2377B0F812820C5 /* ProfileHeaderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98FC406CB025F46041481 /* ProfileHeaderInfoView.swift */; }; @@ -3794,9 +3817,12 @@ 62E98580ED2041F1BA80D3FB /* AlamofireDefaultSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98D998C0EBCA801E4BD65 /* AlamofireDefaultSessionManager.swift */; }; 62E985A6228659D32EDE2231 /* StreakNotificationsControlPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9840661E3EE43A4552946 /* StreakNotificationsControlPresenter.swift */; }; 62E985BDBC2985E15C3FB4F6 /* ProfileDescriptionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98AFF3B8AC3CB8374445B /* ProfileDescriptionPresenter.swift */; }; + 62E98622F3B03F7282DC06D8 /* AchievementDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98F62D7F739C8E411BB98 /* AchievementDescription.swift */; }; 62E9862FC6DD9EC1AC1647DA /* CodeEditorSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98F34F48CF83CC3A6939B /* CodeEditorSettingsViewController.swift */; }; 62E9868BAC8E63E53A9BD0AE /* PinsMapPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98750EE80BFD97E4C7072 /* PinsMapPresenter.swift */; }; + 62E986991E9352C3266CAE49 /* AchievementProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98C688DF80BD29740D967 /* AchievementProgress.swift */; }; 62E986B9852E979D92D45A41 /* PinsMapBlockContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E985F73C3288F8D0045327 /* PinsMapBlockContentView.swift */; }; + 62E986E3352543F9ED07093F /* NotificationBannerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98C84F59654D91C200A81 /* NotificationBannerType.swift */; }; 62E986E416361591CE91DE3B /* CourseListViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9865A5FE3936F4ADA7133 /* CourseListViewData.swift */; }; 62E9870AFED7781931340685 /* ProfileDescriptionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98AFF3B8AC3CB8374445B /* ProfileDescriptionPresenter.swift */; }; 62E9872D206BC9D2C355B11A /* ProfileMenuBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E980E9DFE93B405A0E48AD /* ProfileMenuBlock.swift */; }; @@ -3805,6 +3831,7 @@ 62E9877EE8A8612EDBD7A6BE /* ProfileViewController+StreakNotificationsControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9888C0199CE31C6B4E91D /* ProfileViewController+StreakNotificationsControlView.swift */; }; 62E9879A89A2EFFA5CCAED86 /* StepikURLSessionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98DC7237B8757A9F58935 /* StepikURLSessionConfiguration.swift */; }; 62E987A1CC2316014302E314 /* ProfileMenuBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E980E9DFE93B405A0E48AD /* ProfileMenuBlock.swift */; }; + 62E987CB113F6D20B108D070 /* AchievementPopupAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9851360F1490A8311EC41 /* AchievementPopupAlertManager.swift */; }; 62E987E79C773F8C291F49F3 /* PinsMapBlockContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E985F73C3288F8D0045327 /* PinsMapBlockContentView.swift */; }; 62E9883EB54C3849E60F905B /* UIView+fromNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E982BB5246C2BBC5E88D06 /* UIView+fromNib.swift */; }; 62E98873E98E4598D8F1ACB0 /* StepikURLSessionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98DC7237B8757A9F58935 /* StepikURLSessionConfiguration.swift */; }; @@ -3816,6 +3843,7 @@ 62E9897FBB60991C568BFDBE /* UITableView+TableHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98D9D45E6DA6099AF1C0B /* UITableView+TableHeaderLayout.swift */; }; 62E989BDE0A04F5A099CE905 /* ProfileHeaderInfoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62E98801330B94B34FA0D7B9 /* ProfileHeaderInfoView.xib */; }; 62E98A020CCA8FBA72EAE811 /* ProfileViewController+StreakNotificationsControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9888C0199CE31C6B4E91D /* ProfileViewController+StreakNotificationsControlView.swift */; }; + 62E98A973A47105C195530CD /* AchievementsRetriever.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E986C77C97CA8353D93E25 /* AchievementsRetriever.swift */; }; 62E98AB201BD9C1AFEAFA88E /* PinsMapBlockContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E985F73C3288F8D0045327 /* PinsMapBlockContentView.swift */; }; 62E98ABEDEB0D955D6F3A951 /* UserAgentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E987B9D418CC2175A64EC2 /* UserAgentTests.swift */; }; 62E98AF2CF2F93395EBBBC11 /* StepikURLSessionConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98DC7237B8757A9F58935 /* StepikURLSessionConfiguration.swift */; }; @@ -3823,6 +3851,7 @@ 62E98B4E497E13CB040A5C2A /* PinsMapPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98750EE80BFD97E4C7072 /* PinsMapPresenter.swift */; }; 62E98B4F07F2A40F3D385F5A /* WrappingNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98E55BD59203DE01307B1 /* WrappingNavigationViewController.swift */; }; 62E98B7E75DC928DA044BC5D /* UIView+fromNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E982BB5246C2BBC5E88D06 /* UIView+fromNib.swift */; }; + 62E98B8919D18CD0F8238F97 /* Collection+SafeSubscript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98E4C4AECA5B066006DE7 /* Collection+SafeSubscript.swift */; }; 62E98B94B2E3FE4ABB80AE41 /* ProfileHeaderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98FC406CB025F46041481 /* ProfileHeaderInfoView.swift */; }; 62E98BB5A0A3D643A07C837B /* StreakNotificationsControlPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9840661E3EE43A4552946 /* StreakNotificationsControlPresenter.swift */; }; 62E98BC4B9330D5A2FFBC9B5 /* CodeEditorSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98F34F48CF83CC3A6939B /* CodeEditorSettingsViewController.swift */; }; @@ -3832,6 +3861,7 @@ 62E98C1377148A52D15AAEBF /* CodeEditorPreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98148BF3C6F51F47F2BA9 /* CodeEditorPreferencesContainer.swift */; }; 62E98C1F76B23AFDE100CF6B /* ProfileHeaderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98FC406CB025F46041481 /* ProfileHeaderInfoView.swift */; }; 62E98C6B6DE6E2BD98829A26 /* UITableView+TableHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98D9D45E6DA6099AF1C0B /* UITableView+TableHeaderLayout.swift */; }; + 62E98C73672CAA20DEB33CAC /* AchievementsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98F54E53E13FA7F6EDB82 /* AchievementsAPI.swift */; }; 62E98C88FB132BCDFD88F8AE /* PinsMapPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98750EE80BFD97E4C7072 /* PinsMapPresenter.swift */; }; 62E98C89C492DD7D50D5AB24 /* WrappingNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98E55BD59203DE01307B1 /* WrappingNavigationViewController.swift */; }; 62E98C975482E46A95E173AB /* AlamofireDefaultSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98D998C0EBCA801E4BD65 /* AlamofireDefaultSessionManager.swift */; }; @@ -3840,14 +3870,17 @@ 62E98D07F2F12EF7A3CB3E09 /* ProfileHeaderInfoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 62E98801330B94B34FA0D7B9 /* ProfileHeaderInfoView.xib */; }; 62E98D1D5AE08AC53898A59B /* CodeEditorPreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98148BF3C6F51F47F2BA9 /* CodeEditorPreferencesContainer.swift */; }; 62E98D42A45F833D1371E4A6 /* UIView+fromNib.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E982BB5246C2BBC5E88D06 /* UIView+fromNib.swift */; }; + 62E98D686165FCA5E588FDD5 /* CustomNotificationBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9810DF187ACA3C26F5396 /* CustomNotificationBanner.swift */; }; 62E98D7EC355992DB8889003 /* UITableView+TableHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98D9D45E6DA6099AF1C0B /* UITableView+TableHeaderLayout.swift */; }; 62E98D9D4008CA06FC1E5446 /* ProfileViewController+StreakNotificationsControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9888C0199CE31C6B4E91D /* ProfileViewController+StreakNotificationsControlView.swift */; }; 62E98DC371227AE0EDF94038 /* CodeEditorPreferencesContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98148BF3C6F51F47F2BA9 /* CodeEditorPreferencesContainer.swift */; }; 62E98DD20F2FC4D7DA37DF9A /* AlamofireDefaultSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98D998C0EBCA801E4BD65 /* AlamofireDefaultSessionManager.swift */; }; + 62E98DE7FA0EE5EB23136B52 /* AchievementsListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98AE968B12714D825650A /* AchievementsListPresenter.swift */; }; 62E98E207924FBDBAD355E57 /* WrappingNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98E55BD59203DE01307B1 /* WrappingNavigationViewController.swift */; }; 62E98E2FC787CFDAAF38CB52 /* PinsMapPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98750EE80BFD97E4C7072 /* PinsMapPresenter.swift */; }; 62E98E53B559035BD0E4D903 /* CodeEditorSettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E989579A70D89D559F7520 /* CodeEditorSettingsPresenter.swift */; }; 62E98E72A85E731394868512 /* CourseListViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9865A5FE3936F4ADA7133 /* CourseListViewData.swift */; }; + 62E98E72EB42F2A772BF9760 /* AchievementProgressesAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98B1DA8236C4C5E5B89A2 /* AchievementProgressesAPI.swift */; }; 62E98E8C35AE5A7F3A2508E4 /* ProfileHeaderInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E98FC406CB025F46041481 /* ProfileHeaderInfoView.swift */; }; 62E98EB2034914D78C4A5F14 /* StreakNotificationsControlPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9840661E3EE43A4552946 /* StreakNotificationsControlPresenter.swift */; }; 62E98EE5F0C3EF9E991C8AD3 /* StreakNotificationsControlPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E9840661E3EE43A4552946 /* StreakNotificationsControlPresenter.swift */; }; @@ -5163,6 +5196,15 @@ 2C1B64D11F4C595F00236804 /* AdaptiveInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AdaptiveInfo.plist; path = Content/3124/AdaptiveInfo.plist; sourceTree = ""; }; 2C1B64D31F4C595F00236804 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon.png; path = Content/3124/icon.png; sourceTree = ""; }; 2C1B64E61F4C5A6D00236804 /* Icons.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Icons.xcassets; path = Content/3124/Icons.xcassets; sourceTree = ""; }; + 2C22042420E277B40060117A /* Skeletonable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Skeletonable.swift; sourceTree = ""; }; + 2C22042620E277E50060117A /* SkeletonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkeletonView.swift; sourceTree = ""; }; + 2C22042820E277F60060117A /* SkeletonViewAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkeletonViewAnimation.swift; sourceTree = ""; }; + 2C22042920E277F60060117A /* Skeletonable+UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Skeletonable+UIView.swift"; sourceTree = ""; }; + 2C22042A20E277F60060117A /* Skeletonable+UITableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Skeletonable+UITableView.swift"; sourceTree = ""; }; + 2C22042F20E27E400060117A /* ProfileCellSkeletonPlaceholderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfileCellSkeletonPlaceholderView.xib; sourceTree = ""; }; + 2C22043120E2804F0060117A /* AchievementSkeletonPlaceholderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AchievementSkeletonPlaceholderView.xib; sourceTree = ""; }; + 2C22043320E28AD50060117A /* AchievementListSkeletonPlaceholderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AchievementListSkeletonPlaceholderView.xib; sourceTree = ""; }; + 2C22043520E2A0860060117A /* ProfileAchievementsContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfileAchievementsContentView.xib; sourceTree = ""; }; 2C23C5DD1F6AB10800FC2B7C /* SocialAuthPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthPresenter.swift; sourceTree = ""; }; 2C23C5DF1F6AB2AD00FC2B7C /* SocialAuthProviders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthProviders.swift; sourceTree = ""; }; 2C23C5E11F6BD43300FC2B7C /* EmailAuthPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailAuthPresenter.swift; sourceTree = ""; }; @@ -5214,6 +5256,10 @@ 2C6640D81F30BC4D0033A274 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/AdaptiveMain.strings; sourceTree = ""; }; 2C6640DA1F30BC4E0033A274 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AdaptiveMain.strings; sourceTree = ""; }; 2C6640E31F30BCE60033A274 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/AchievementTableViewCell.xib; sourceTree = ""; }; + 2C6B2F9A20D7F24800F7C976 /* AchievementPopupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementPopupViewController.swift; sourceTree = ""; }; + 2C6B2F9B20D7F24800F7C976 /* AchievementPopupViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AchievementPopupViewController.xib; sourceTree = ""; }; + 2C6B2F9F20D95B2800F7C976 /* AchievementNotificationBannerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementNotificationBannerView.swift; sourceTree = ""; }; + 2C6B2FA020D95B2800F7C976 /* AchievementNotificationBannerView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AchievementNotificationBannerView.xib; sourceTree = ""; }; 2C6E9CD31FED657E001821A2 /* Adaptive.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Adaptive.storyboard; sourceTree = ""; }; 2C6E9CDA1FF27543001821A2 /* AdaptiveStorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveStorageManager.swift; sourceTree = ""; }; 2C733C391F29E090000E7FAF /* AdaptiveStatsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AdaptiveStatsManager.swift; path = Stepic/AdaptiveStatsManager.swift; sourceTree = SOURCE_ROOT; }; @@ -5303,6 +5349,8 @@ 2CB9E8CE1F839C8E0004E17F /* NotificationsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsTableViewCell.swift; sourceTree = ""; }; 2CB9E8CF1F839C8E0004E17F /* NotificationsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NotificationsTableViewCell.xib; sourceTree = ""; }; 2CBC4C051F1E4A1300FE96C4 /* Config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Config.plist; sourceTree = ""; }; + 2CBCBD4820D1AAFC000B5732 /* AchievementsListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AchievementsListTableViewCell.swift; sourceTree = ""; }; + 2CBCBD4920D1AAFC000B5732 /* AchievementsListTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AchievementsListTableViewCell.xib; sourceTree = ""; }; 2CBD855B201799B700E14F83 /* AdaptiveRatingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveRatingsViewController.swift; sourceTree = ""; }; 2CC0754620177A2E004A6005 /* AdaptiveStatsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveStatsViewController.swift; sourceTree = ""; }; 2CC351851F6827BE004255B6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Auth.storyboard; sourceTree = ""; }; @@ -5344,6 +5392,12 @@ 2CE3BCA61FBF13CE000AD405 /* SQLReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SQLReply.swift; sourceTree = ""; }; 2CE527C02029D8EE0047EC5F /* AdaptiveStatsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveStatsSection.swift; sourceTree = ""; }; 2CE527C42029D9780047EC5F /* AdaptiveAdaptiveStatsPagerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptiveAdaptiveStatsPagerViewController.swift; sourceTree = ""; }; + 2CE8390620C7F16700FE3672 /* ContentMenuBlockTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentMenuBlockTableViewCell.swift; sourceTree = ""; }; + 2CE8390720C7F16700FE3672 /* ContentMenuBlockTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ContentMenuBlockTableViewCell.xib; sourceTree = ""; }; + 2CE8390B20C8094000FE3672 /* ProfileAchievementsContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileAchievementsContentView.swift; sourceTree = ""; }; + 2CE8390D20C8096400FE3672 /* ProfileAchievementsPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileAchievementsPresenter.swift; sourceTree = ""; }; + 2CE8391020C8101500FE3672 /* AchievementBadgeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AchievementBadgeView.xib; sourceTree = ""; }; + 2CE8391220C8102200FE3672 /* AchievementBadgeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementBadgeView.swift; sourceTree = ""; }; 2CEDA35F1F336FEC005F4A5D /* AdaptiveRatingHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AdaptiveRatingHelper.swift; path = Stepic/AdaptiveRatingHelper.swift; sourceTree = SOURCE_ROOT; }; 2CF08859205BEBF500FCB9C0 /* StepikTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepikTableView.swift; sourceTree = ""; }; 2CF0885C205BED9700FCB9C0 /* StepikPlaceholderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StepikPlaceholderView.xib; sourceTree = ""; }; @@ -5369,23 +5423,35 @@ 60775FB2AF00F82D3077ABE1 /* Pods_SberbankUniversity.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SberbankUniversity.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 611F6148D39767518C0599A3 /* Pods-Adaptive 3124 Screenshots.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adaptive 3124 Screenshots.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Adaptive 3124 Screenshots/Pods-Adaptive 3124 Screenshots.debug.xcconfig"; sourceTree = ""; }; 62E980E9DFE93B405A0E48AD /* ProfileMenuBlock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileMenuBlock.swift; sourceTree = ""; }; + 62E9810DF187ACA3C26F5396 /* CustomNotificationBanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNotificationBanner.swift; sourceTree = ""; }; 62E98148BF3C6F51F47F2BA9 /* CodeEditorPreferencesContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditorPreferencesContainer.swift; sourceTree = ""; }; 62E98275C20698D3770CD6D8 /* ProfileInfoPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileInfoPresenter.swift; sourceTree = ""; }; 62E982BB5246C2BBC5E88D06 /* UIView+fromNib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+fromNib.swift"; sourceTree = ""; }; + 62E983ECB91B2F753DC02E22 /* AchievementsListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementsListViewController.swift; sourceTree = ""; }; 62E9840661E3EE43A4552946 /* StreakNotificationsControlPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreakNotificationsControlPresenter.swift; sourceTree = ""; }; + 62E9851360F1490A8311EC41 /* AchievementPopupAlertManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementPopupAlertManager.swift; sourceTree = ""; }; 62E985F73C3288F8D0045327 /* PinsMapBlockContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinsMapBlockContentView.swift; sourceTree = ""; }; 62E9865A5FE3936F4ADA7133 /* CourseListViewData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseListViewData.swift; sourceTree = ""; }; + 62E986C77C97CA8353D93E25 /* AchievementsRetriever.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementsRetriever.swift; sourceTree = ""; }; + 62E98708CFECD3F1D0AD4120 /* Achievement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Achievement.swift; sourceTree = ""; }; 62E98750EE80BFD97E4C7072 /* PinsMapPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinsMapPresenter.swift; sourceTree = ""; }; 62E987B9D418CC2175A64EC2 /* UserAgentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAgentTests.swift; sourceTree = ""; }; 62E98801330B94B34FA0D7B9 /* ProfileHeaderInfoView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfileHeaderInfoView.xib; sourceTree = ""; }; 62E9888C0199CE31C6B4E91D /* ProfileViewController+StreakNotificationsControlView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ProfileViewController+StreakNotificationsControlView.swift"; sourceTree = ""; }; 62E989579A70D89D559F7520 /* CodeEditorSettingsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditorSettingsPresenter.swift; sourceTree = ""; }; + 62E98AE968B12714D825650A /* AchievementsListPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementsListPresenter.swift; sourceTree = ""; }; 62E98AFF3B8AC3CB8374445B /* ProfileDescriptionPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileDescriptionPresenter.swift; sourceTree = ""; }; + 62E98B1DA8236C4C5E5B89A2 /* AchievementProgressesAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementProgressesAPI.swift; sourceTree = ""; }; + 62E98C688DF80BD29740D967 /* AchievementProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementProgress.swift; sourceTree = ""; }; + 62E98C84F59654D91C200A81 /* NotificationBannerType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationBannerType.swift; sourceTree = ""; }; 62E98D998C0EBCA801E4BD65 /* AlamofireDefaultSessionManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlamofireDefaultSessionManager.swift; sourceTree = ""; }; 62E98D9D45E6DA6099AF1C0B /* UITableView+TableHeaderLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+TableHeaderLayout.swift"; sourceTree = ""; }; 62E98DC7237B8757A9F58935 /* StepikURLSessionConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StepikURLSessionConfiguration.swift; sourceTree = ""; }; + 62E98E4C4AECA5B066006DE7 /* Collection+SafeSubscript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+SafeSubscript.swift"; sourceTree = ""; }; 62E98E55BD59203DE01307B1 /* WrappingNavigationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrappingNavigationViewController.swift; sourceTree = ""; }; 62E98F34F48CF83CC3A6939B /* CodeEditorSettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeEditorSettingsViewController.swift; sourceTree = ""; }; + 62E98F54E53E13FA7F6EDB82 /* AchievementsAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementsAPI.swift; sourceTree = ""; }; + 62E98F62D7F739C8E411BB98 /* AchievementDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementDescription.swift; sourceTree = ""; }; 62E98FC406CB025F46041481 /* ProfileHeaderInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileHeaderInfoView.swift; sourceTree = ""; }; 6407CA973D1E0DD43D7D37A6 /* Pods-Adaptive 3149 Screenshots.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Adaptive 3149 Screenshots.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Adaptive 3149 Screenshots/Pods-Adaptive 3149 Screenshots.debug.xcconfig"; sourceTree = ""; }; 659D078D6ECC6EAC850691ED /* Pods-Stepic.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stepic.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Stepic/Pods-Stepic.debug.xcconfig"; sourceTree = ""; }; @@ -6338,6 +6404,8 @@ 08D5F57F1F7DBA70007C1634 /* CourseReviewSummariesAPI.swift */, 087585A21FB50D640047A269 /* CourseListsAPI.swift */, 86624A721FC76578008E7E6C /* NotificationStatusesAPI.swift */, + 62E98F54E53E13FA7F6EDB82 /* AchievementsAPI.swift */, + 62E98B1DA8236C4C5E5B89A2 /* AchievementProgressesAPI.swift */, ); name = "API Endpoints"; sourceTree = ""; @@ -6532,6 +6600,8 @@ 086E965C1BF6682000AB952D /* Views */ = { isa = PBXGroup; children = ( + 2C22042320E277980060117A /* Skeleton */, + 2CE8390F20C80FF200FE3672 /* AchievementBadge */, 2C453396204D46B70061342A /* PinsMap */, 2C9704221F3B1BFE00C36F0A /* TapProxyView.swift */, 08F485911C574CFC000165AA /* VideoDownloadView */, @@ -6545,6 +6615,7 @@ 08EDB8B91F7C3D100028A9AE /* CourseWidgetView */, 2CF08859205BEBF500FCB9C0 /* StepikTableView.swift */, 08DDF90E20A9B2D0004ECC11 /* PersonalDeadlinesSuggestionWidgetView */, + 62E9851EC1CEC1B2694CEA00 /* NotificationBanner */, ); name = Views; sourceTree = ""; @@ -6590,6 +6661,8 @@ 08D9E98B206C243D002F41D3 /* DatabaseFetchService.swift */, 083E49DC2072B684004896C0 /* IDFetchable.swift */, 089587F420A20C5D003BAFB3 /* Enrollment.swift */, + 62E98708CFECD3F1D0AD4120 /* Achievement.swift */, + 62E98C688DF80BD29740D967 /* AchievementProgress.swift */, ); name = Model; sourceTree = ""; @@ -6843,6 +6916,7 @@ 62E9871E9ADE76771F9158E4 /* StreakNotificationsControl */, 62E98E1032DDB6DD34A6D5D6 /* ProfileDescription */, 62E982A1EDB07B5DD63EC36C /* PinsMap */, + 2CE8390A20C8088F00FE3672 /* Achievements */, ); name = Profile; sourceTree = ""; @@ -6984,7 +7058,8 @@ 08CBA3091F57558200302154 /* MenuBlockCells */ = { isa = PBXGroup; children = ( - 2CB2393B20BB455200F0FE3B /* GenericContent */, + 2CE8390320C7EF5A00FE3672 /* GenericContent */, + 2CB2393B20BB455200F0FE3B /* GenericExpandableContent */, 2C92669420B5C78900525AFC /* PlaceholderCell */, 08DF78BB1F5EEFFE00AEEA85 /* MenuBlockTableViewCell.swift */, 08DF78C41F5EF02300AEEA85 /* Switch */, @@ -7310,6 +7385,7 @@ 086FC6A71FE04DBD00C7DFF4 /* RangeExtension.swift */, 62E982BB5246C2BBC5E88D06 /* UIView+fromNib.swift */, 62E98D9D45E6DA6099AF1C0B /* UITableView+TableHeaderLayout.swift */, + 62E98E4C4AECA5B066006DE7 /* Collection+SafeSubscript.swift */, ); name = Extensions; sourceTree = ""; @@ -7765,6 +7841,29 @@ name = Onboarding; sourceTree = ""; }; + 2C22042320E277980060117A /* Skeleton */ = { + isa = PBXGroup; + children = ( + 2C22042E20E27E120060117A /* Views */, + 2C22042420E277B40060117A /* Skeletonable.swift */, + 2C22042620E277E50060117A /* SkeletonView.swift */, + 2C22042A20E277F60060117A /* Skeletonable+UITableView.swift */, + 2C22042920E277F60060117A /* Skeletonable+UIView.swift */, + 2C22042820E277F60060117A /* SkeletonViewAnimation.swift */, + ); + name = Skeleton; + sourceTree = ""; + }; + 2C22042E20E27E120060117A /* Views */ = { + isa = PBXGroup; + children = ( + 2C22042F20E27E400060117A /* ProfileCellSkeletonPlaceholderView.xib */, + 2C22043120E2804F0060117A /* AchievementSkeletonPlaceholderView.xib */, + 2C22043320E28AD50060117A /* AchievementListSkeletonPlaceholderView.xib */, + ); + name = Views; + sourceTree = ""; + }; 2C2544071F348085004DB3D9 /* AchievementNotificationView */ = { isa = PBXGroup; children = ( @@ -7903,6 +8002,25 @@ name = AchievementTableViewCell; sourceTree = ""; }; + 2C6B2F9520D7F1C000F7C976 /* AchievementAlert */ = { + isa = PBXGroup; + children = ( + 2C6B2F9A20D7F24800F7C976 /* AchievementPopupViewController.swift */, + 2C6B2F9B20D7F24800F7C976 /* AchievementPopupViewController.xib */, + 62E9851360F1490A8311EC41 /* AchievementPopupAlertManager.swift */, + ); + name = AchievementAlert; + sourceTree = ""; + }; + 2C6B2F9E20D95B1500F7C976 /* AchievementNotificationBanner */ = { + isa = PBXGroup; + children = ( + 2C6B2F9F20D95B2800F7C976 /* AchievementNotificationBannerView.swift */, + 2C6B2FA020D95B2800F7C976 /* AchievementNotificationBannerView.xib */, + ); + name = AchievementNotificationBanner; + sourceTree = ""; + }; 2C6E9CD91FF2752B001821A2 /* Helpers */ = { isa = PBXGroup; children = ( @@ -8083,13 +8201,13 @@ path = "Adaptive leaderboards names"; sourceTree = ""; }; - 2CB2393B20BB455200F0FE3B /* GenericContent */ = { + 2CB2393B20BB455200F0FE3B /* GenericExpandableContent */ = { isa = PBXGroup; children = ( 2CB2393C20BB45F100F0FE3B /* ContentExpandableMenuBlockTableViewCell.swift */, 2CB2393D20BB45F100F0FE3B /* ContentExpandableMenuBlockTableViewCell.xib */, ); - name = GenericContent; + name = GenericExpandableContent; sourceTree = ""; }; 2CB62BF02019FDB300B5E336 /* Adaptive onboarding content */ = { @@ -8139,6 +8257,15 @@ name = NotificationsTableViewCell; sourceTree = ""; }; + 2CBCBD4720D1AAD5000B5732 /* Achievement Cell */ = { + isa = PBXGroup; + children = ( + 2CBCBD4820D1AAFC000B5732 /* AchievementsListTableViewCell.swift */, + 2CBCBD4920D1AAFC000B5732 /* AchievementsListTableViewCell.xib */, + ); + name = "Achievement Cell"; + sourceTree = ""; + }; 2CBD855D201799BE00E14F83 /* AdaptiveStats */ = { isa = PBXGroup; children = ( @@ -8291,6 +8418,39 @@ name = Achievements; sourceTree = ""; }; + 2CE8390320C7EF5A00FE3672 /* GenericContent */ = { + isa = PBXGroup; + children = ( + 2CE8390620C7F16700FE3672 /* ContentMenuBlockTableViewCell.swift */, + 2CE8390720C7F16700FE3672 /* ContentMenuBlockTableViewCell.xib */, + ); + name = GenericContent; + sourceTree = ""; + }; + 2CE8390A20C8088F00FE3672 /* Achievements */ = { + isa = PBXGroup; + children = ( + 2C6B2F9E20D95B1500F7C976 /* AchievementNotificationBanner */, + 2C6B2F9520D7F1C000F7C976 /* AchievementAlert */, + 2CE8390B20C8094000FE3672 /* ProfileAchievementsContentView.swift */, + 2C22043520E2A0860060117A /* ProfileAchievementsContentView.xib */, + 2CE8390D20C8096400FE3672 /* ProfileAchievementsPresenter.swift */, + 62E986C77C97CA8353D93E25 /* AchievementsRetriever.swift */, + 62E98F62D7F739C8E411BB98 /* AchievementDescription.swift */, + 62E98AC3C03DF7EDB2FD4AB6 /* List */, + ); + name = Achievements; + sourceTree = ""; + }; + 2CE8390F20C80FF200FE3672 /* AchievementBadge */ = { + isa = PBXGroup; + children = ( + 2CE8391220C8102200FE3672 /* AchievementBadgeView.swift */, + 2CE8391020C8101500FE3672 /* AchievementBadgeView.xib */, + ); + name = AchievementBadge; + sourceTree = ""; + }; 2CEDA35E1F336F0E005F4A5D /* Managers */ = { isa = PBXGroup; children = ( @@ -8364,6 +8524,15 @@ name = PinsMap; sourceTree = ""; }; + 62E9851EC1CEC1B2694CEA00 /* NotificationBanner */ = { + isa = PBXGroup; + children = ( + 62E98C84F59654D91C200A81 /* NotificationBannerType.swift */, + 62E9810DF187ACA3C26F5396 /* CustomNotificationBanner.swift */, + ); + name = NotificationBanner; + sourceTree = ""; + }; 62E9871E9ADE76771F9158E4 /* StreakNotificationsControl */ = { isa = PBXGroup; children = ( @@ -8373,6 +8542,16 @@ name = StreakNotificationsControl; sourceTree = ""; }; + 62E98AC3C03DF7EDB2FD4AB6 /* List */ = { + isa = PBXGroup; + children = ( + 2CBCBD4720D1AAD5000B5732 /* Achievement Cell */, + 62E983ECB91B2F753DC02E22 /* AchievementsListViewController.swift */, + 62E98AE968B12714D825650A /* AchievementsListPresenter.swift */, + ); + name = List; + sourceTree = ""; + }; 62E98E1032DDB6DD34A6D5D6 /* ProfileDescription */ = { isa = PBXGroup; children = ( @@ -9279,6 +9458,7 @@ 2CF4252E2024C10D002D7305 /* overlay_simple.png in Resources */, 2CB2393F20BB45F100F0FE3B /* ContentExpandableMenuBlockTableViewCell.xib in Resources */, 2CF4252D2024C10D002D7305 /* overlay_hard.png in Resources */, + 2C22043020E27E400060117A /* ProfileCellSkeletonPlaceholderView.xib in Resources */, 2CA9D98F2012334B007AA743 /* nouns_m.plist in Resources */, 087854F51DF6EE0D00368A50 /* wysiwyg.css in Resources */, 08DF1D961BDADB7A00BA35EA /* Scripts.plist in Resources */, @@ -9303,6 +9483,7 @@ 084C9AF81EC4C11A00AC5C3C /* jquery-3.2.1.min.js in Resources */, 082B54D120A9DD3C00144817 /* PersonalDeadlines.storyboard in Resources */, 08EB85F81D10454D00E4F345 /* DiscussionsStoryboard.storyboard in Resources */, + 2C22043220E2804F0060117A /* AchievementSkeletonPlaceholderView.xib in Resources */, 2CA9D98D20123347007AA743 /* adjectives_m.plist in Resources */, 083F2B261E9EC10A00714173 /* LoadingPaginationView.xib in Resources */, 2C7FEE6F1FDAF8F600B2B4F1 /* Onboarding.storyboard in Resources */, @@ -9317,6 +9498,7 @@ 08F485951C574DAD000165AA /* WarningView.xib in Resources */, 0829ED9620C865BB0018E6FF /* ContentLanguagePreferenceTableViewCell.xib in Resources */, 089056131E98021000B8FE6A /* RateAppViewController.xib in Resources */, + 2CE8391120C8101500FE3672 /* AchievementBadgeView.xib in Resources */, 0869F6D81CE3684000F8A6DB /* default_sound.wav in Resources */, 2C92669820B5C7CF00525AFC /* PlaceholderTableViewCell.xib in Resources */, 08CB0D441FB63F74001A1E02 /* ContentLanguagesView.xib in Resources */, @@ -9327,6 +9509,7 @@ 083F2B111E9D8E8F00714173 /* CertificatesStoryboard.storyboard in Resources */, 0888D0F91F1E3CEF00A16863 /* CodeInputAccessoryView.xib in Resources */, 2CB62BEE2019FDB100B5E336 /* arrow_right.svg in Resources */, + 2C6B2FA220D95B2800F7C976 /* AchievementNotificationBannerView.xib in Resources */, 2CA9D98A2011FBD5007AA743 /* LeaderboardTableViewCell.xib in Resources */, 08CA59DD1BBC00B9008DC44D /* TeacherCollectionViewCell.xib in Resources */, 2CB62BE52019FD8500B5E336 /* step1.html in Resources */, @@ -9337,6 +9520,7 @@ 080C5E721EFC094F0036EB3D /* CodeQuizToolbarView.xib in Resources */, 08195A061FA00FF400E6D6CD /* CourseLists.storyboard in Resources */, 085DF8D01C99A6F7006809D9 /* PlayerTestStoryboard.storyboard in Resources */, + 2CE8390920C7F16700FE3672 /* ContentMenuBlockTableViewCell.xib in Resources */, 2CB62BE42019FD8500B5E336 /* step3.html in Resources */, 2CB62BEF2019FDB100B5E336 /* arrow_left.svg in Resources */, 08A9F7151FC3837800640F1F /* CourseTagCollectionViewCell.xib in Resources */, @@ -9346,6 +9530,7 @@ 08A7B0E81EB340F500C1BA71 /* Auth.plist in Resources */, 083F2B231E9E645000714173 /* CertificateTableViewCell.xib in Resources */, 08EF9A0A1C91D0F800433E4A /* StepicVideoPlayerViewController.xib in Resources */, + 2C22043620E2A0860060117A /* ProfileAchievementsContentView.xib in Resources */, 0869F6D51CE229C800F8A6DB /* Queues.plist in Resources */, 2CF0885D205BED9700FCB9C0 /* StepikPlaceholderView.xib in Resources */, 2C7FEE771FDAF9D600B2B4F1 /* onboardingAnimation3.json in Resources */, @@ -9353,7 +9538,10 @@ 2C6E9CD41FED657E001821A2 /* Adaptive.storyboard in Resources */, 08AC213C1CDD35DF00FBB9CD /* Users.plist in Resources */, 0888D1051F1E41FF00A16863 /* CodeInputAccessoryCollectionViewCell.xib in Resources */, + 2C22043420E28AD50060117A /* AchievementListSkeletonPlaceholderView.xib in Resources */, 62E9815A6AAC6C12E9B9D862 /* ProfileHeaderInfoView.xib in Resources */, + 2C6B2F9D20D7F24800F7C976 /* AchievementPopupViewController.xib in Resources */, + 2CBCBD4B20D1AAFC000B5732 /* AchievementsListTableViewCell.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -10144,6 +10332,8 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", "${BUILT_PRODUCTS_DIR}/Presentr/Presentr.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit-iOS/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", @@ -10151,7 +10341,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage.default-GIF/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVGKit-iOS/SVGKit.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SkeletonView/SkeletonView.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/TSMessages/TSMessages.framework", "${BUILT_PRODUCTS_DIR}/TTTAttributedLabel/TTTAttributedLabel.framework", @@ -10162,9 +10352,6 @@ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/pop/pop.framework", "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework", - "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", - "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", - "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -10190,6 +10377,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Koloda.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Presentr.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", @@ -10197,7 +10386,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVGKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SkeletonView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TSMessages.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TTTAttributedLabel.framework", @@ -10208,9 +10397,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pop.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -10338,6 +10524,8 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", "${BUILT_PRODUCTS_DIR}/Presentr/Presentr.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit-iOS/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", @@ -10345,7 +10533,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage.default-GIF/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVGKit-iOS/SVGKit.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SkeletonView/SkeletonView.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/TSMessages/TSMessages.framework", "${BUILT_PRODUCTS_DIR}/TTTAttributedLabel/TTTAttributedLabel.framework", @@ -10356,9 +10544,6 @@ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/pop/pop.framework", "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework", - "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", - "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", - "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -10384,6 +10569,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Koloda.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Presentr.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", @@ -10391,7 +10578,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVGKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SkeletonView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TSMessages.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TTTAttributedLabel.framework", @@ -10402,9 +10589,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pop.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -10643,6 +10827,8 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", "${BUILT_PRODUCTS_DIR}/Presentr/Presentr.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit-iOS/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", @@ -10650,7 +10836,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage.default-GIF/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVGKit-iOS/SVGKit.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SkeletonView/SkeletonView.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/TSMessages/TSMessages.framework", "${BUILT_PRODUCTS_DIR}/TTTAttributedLabel/TTTAttributedLabel.framework", @@ -10661,9 +10847,6 @@ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/pop/pop.framework", "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework", - "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", - "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", - "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -10689,6 +10872,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Koloda.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Presentr.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", @@ -10696,7 +10881,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVGKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SkeletonView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TSMessages.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TTTAttributedLabel.framework", @@ -10707,9 +10892,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pop.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -10797,6 +10979,8 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", "${BUILT_PRODUCTS_DIR}/Presentr/Presentr.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit-iOS/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", @@ -10804,7 +10988,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage.default-GIF/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVGKit-iOS/SVGKit.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SkeletonView/SkeletonView.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/TSMessages/TSMessages.framework", "${BUILT_PRODUCTS_DIR}/TTTAttributedLabel/TTTAttributedLabel.framework", @@ -10815,9 +10999,6 @@ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/pop/pop.framework", "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework", - "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", - "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", - "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -10843,6 +11024,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Koloda.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Presentr.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", @@ -10850,7 +11033,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVGKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SkeletonView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TSMessages.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TTTAttributedLabel.framework", @@ -10861,9 +11044,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pop.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -11055,6 +11235,8 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", "${BUILT_PRODUCTS_DIR}/Presentr/Presentr.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit-iOS/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", @@ -11062,7 +11244,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage.default-GIF/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVGKit-iOS/SVGKit.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SkeletonView/SkeletonView.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/TSMessages/TSMessages.framework", "${BUILT_PRODUCTS_DIR}/TTTAttributedLabel/TTTAttributedLabel.framework", @@ -11073,9 +11255,6 @@ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/pop/pop.framework", "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework", - "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", - "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", - "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -11101,6 +11280,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Koloda.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Presentr.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", @@ -11108,7 +11289,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVGKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SkeletonView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TSMessages.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TTTAttributedLabel.framework", @@ -11119,9 +11300,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pop.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -11175,6 +11353,8 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", "${BUILT_PRODUCTS_DIR}/Presentr/Presentr.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit-iOS/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", @@ -11182,7 +11362,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage.default-GIF/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVGKit-iOS/SVGKit.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SkeletonView/SkeletonView.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/TSMessages/TSMessages.framework", "${BUILT_PRODUCTS_DIR}/TTTAttributedLabel/TTTAttributedLabel.framework", @@ -11193,9 +11373,6 @@ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/pop/pop.framework", "${BUILT_PRODUCTS_DIR}/FLAnimatedImage/FLAnimatedImage.framework", - "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", - "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", - "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -11221,6 +11398,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Koloda.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Presentr.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", @@ -11228,7 +11407,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVGKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SkeletonView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TSMessages.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TTTAttributedLabel.framework", @@ -11239,9 +11418,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/pop.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLAnimatedImage.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -11313,6 +11489,8 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", "${BUILT_PRODUCTS_DIR}/Presentr/Presentr.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit-iOS/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", @@ -11320,7 +11498,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage-iOS/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVGKit-iOS/SVGKit.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SkeletonView/SkeletonView.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/TSMessages/TSMessages.framework", "${BUILT_PRODUCTS_DIR}/TTTAttributedLabel/TTTAttributedLabel.framework", @@ -11355,6 +11533,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Koloda.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Presentr.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", @@ -11362,7 +11542,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVGKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SkeletonView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TSMessages.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TTTAttributedLabel.framework", @@ -11443,6 +11623,8 @@ "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", "${BUILT_PRODUCTS_DIR}/Kanna/Kanna.framework", "${BUILT_PRODUCTS_DIR}/Koloda/Koloda.framework", + "${BUILT_PRODUCTS_DIR}/MarqueeLabel/MarqueeLabel.framework", + "${BUILT_PRODUCTS_DIR}/NotificationBannerSwift/NotificationBannerSwift.framework", "${BUILT_PRODUCTS_DIR}/Presentr/Presentr.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit-iOS/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", @@ -11450,7 +11632,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage-iOS/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SVGKit-iOS/SVGKit.framework", "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", - "${BUILT_PRODUCTS_DIR}/SkeletonView/SkeletonView.framework", + "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON-iOS/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/TSMessages/TSMessages.framework", "${BUILT_PRODUCTS_DIR}/TTTAttributedLabel/TTTAttributedLabel.framework", @@ -11487,6 +11669,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kanna.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Koloda.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MarqueeLabel.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NotificationBannerSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Presentr.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", @@ -11494,7 +11678,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVGKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SkeletonView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TSMessages.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TTTAttributedLabel.framework", @@ -11598,6 +11782,7 @@ 0891424A1BCEE4EF0000BCB0 /* VideoURL+CoreDataProperties.swift in Sources */, 08AEC3A51CCA69F300FFF29E /* Device.swift in Sources */, 2CB2393E20BB45F100F0FE3B /* ContentExpandableMenuBlockTableViewCell.swift in Sources */, + 2C22042D20E277F70060117A /* Skeletonable+UITableView.swift in Sources */, 089142481BCEE4EF0000BCB0 /* Video+CoreDataProperties.swift in Sources */, 9F9C71541E076B3E00EC8DA3 /* WatchSessionSender.swift in Sources */, 08D1EF701BB5618700BE84E6 /* Model.xcdatamodeld in Sources */, @@ -11713,6 +11898,7 @@ 083AABE81BE8D63D005E1E96 /* Progress+CoreDataProperties.swift in Sources */, 087585B71FB51D840047A269 /* CourseList+CoreDataProperties.swift in Sources */, 0805FE441F0D390B001226B4 /* CodePlaygroundManager.swift in Sources */, + 2C6B2F9C20D7F24800F7C976 /* AchievementPopupViewController.swift in Sources */, 08E8F9741F34E3D5008CF4A1 /* SearchQueriesViewController.swift in Sources */, 08D035211D65A252003515C6 /* AnalyticsEvents.swift in Sources */, 084F7AA71E76EF690088368A /* LastStep.swift in Sources */, @@ -11744,6 +11930,7 @@ 080F74611BD8159F0064AAEA /* TabsInfo.swift in Sources */, 0846B1071EDDED4400D64D77 /* StepOptions+CoreDataProperties.swift in Sources */, 08F4CB281CF5C58C00B15B3D /* WebViewHorizontalScrollHelper.swift in Sources */, + 2C22042C20E277F70060117A /* Skeletonable+UIView.swift in Sources */, 082B54D820AA342D00144817 /* PersonalDeadlineModeCollectionViewCell.swift in Sources */, 08407EC71DE4891D0082C4E7 /* FBSocialSDKProvider.swift in Sources */, 089574561E5B76E700C12D21 /* UIImageView+SVGDownload.swift in Sources */, @@ -11833,6 +12020,7 @@ 080F74641BD9213D0064AAEA /* VideosInfo.swift in Sources */, 08E8F9671F34DD2C008CF4A1 /* SearchQueriesPresenter.swift in Sources */, 08D1EF731BB5636700BE84E6 /* Course+CoreDataProperties.swift in Sources */, + 2C22042720E277E50060117A /* SkeletonView.swift in Sources */, 08F555651C4FF22600C877E8 /* QuizControllerDelegate.swift in Sources */, 08D035251D65B5E5003515C6 /* AnalyticsReporter.swift in Sources */, 0899842F1ECDE19E005C0B27 /* LessonView.swift in Sources */, @@ -11846,6 +12034,7 @@ 08AEC3A71CCA75F400FFF29E /* APIDefaults.swift in Sources */, 080CE1611E9581960089A27F /* SubmissionsAPI.swift in Sources */, 081387E11D7AF7700092E05D /* StyledTabBarViewController.swift in Sources */, + 2C6B2FA120D95B2800F7C976 /* AchievementNotificationBannerView.swift in Sources */, 0829B83A1E9D05AE009B4A84 /* Certificate.swift in Sources */, 08AB82351D74926F00FDEADE /* StepicsAPI.swift in Sources */, 2C9E3F3E1F7A930100DDF1AA /* NotificationsAPI.swift in Sources */, @@ -11870,9 +12059,11 @@ 083F2B211E9E645000714173 /* CertificateTableViewCell.swift in Sources */, 08195A181FA0AF1200E6D6CD /* HorizontalCoursesView.swift in Sources */, 08CA59F21BBFD65E008DC44D /* User.swift in Sources */, + 2C22042520E277B40060117A /* Skeletonable.swift in Sources */, 08C194401ED0A05D00A41B72 /* PagerController.swift in Sources */, 082E5E0E1F46379100F41426 /* ReplyCache.swift in Sources */, 2CC351911F682D47004255B6 /* SocialAuthViewController.swift in Sources */, + 2CE8390C20C8094000FE3672 /* ProfileAchievementsContentView.swift in Sources */, 08CB4ABE1C4D3DCE00D7F918 /* WKWebViewPanelManager.m in Sources */, 08EDD6181F7C607A005203E4 /* CourseWidgetTableViewCell.swift in Sources */, 08AF59F81E6D9BE800423EFF /* UINavigationBar.swift in Sources */, @@ -11934,6 +12125,7 @@ 08263D701DE5F230002E5B7F /* NotificationTimePickerViewController.swift in Sources */, 0885F8581BAAD43300F2A188 /* AuthInfo.swift in Sources */, 2CF0885A205BEBF500FCB9C0 /* StepikTableView.swift in Sources */, + 2CE8391320C8102300FE3672 /* AchievementBadgeView.swift in Sources */, 089B370D1BFA07AF003DC593 /* VideoDownloadDelegate.swift in Sources */, 08F4859A1C57868E000165AA /* TextReply.swift in Sources */, 0828FF831BC800C0000AFEA7 /* JSONSerializable.swift in Sources */, @@ -11980,6 +12172,7 @@ 083F2B2D1E9ECD2400714173 /* CertificatesAPIPaginatedMock.swift in Sources */, 08CA59DC1BBC00B9008DC44D /* TeacherCollectionViewCell.swift in Sources */, 085DF8D81C99C49E006809D9 /* VideoRate.swift in Sources */, + 2CE8390820C7F16700FE3672 /* ContentMenuBlockTableViewCell.swift in Sources */, 0890560D1E97CE1800B8FE6A /* LaunchDefaultsContainer.swift in Sources */, 0828FF8C1BC81F41000AFEA7 /* UnitTableViewCell.swift in Sources */, 08901E701CD111F200D94613 /* VersionUpdateAlertConstructor.swift in Sources */, @@ -12011,7 +12204,9 @@ 08DA79011DB6BB36003491C4 /* ControllerHelperLaunchExtension.swift in Sources */, 083540671CE5FC0E00BDFEA5 /* Notification.swift in Sources */, 08CB0D311FB5F9FC001A1E02 /* ExploreViewController.swift in Sources */, + 2C22042B20E277F70060117A /* SkeletonViewAnimation.swift in Sources */, 086A8B281D21796800F45C45 /* VotesAPI.swift in Sources */, + 2CBCBD4A20D1AAFC000B5732 /* AchievementsListTableViewCell.swift in Sources */, 2CA9D97D20109C07007AA743 /* AdaptiveRatingsPresenter.swift in Sources */, 083622D81CD1FA4800CD8915 /* StepTabView.swift in Sources */, 0868C17E1F375A1200D77C5F /* SearchQueriesPersistentManager.swift in Sources */, @@ -12048,6 +12243,7 @@ 62E98C1377148A52D15AAEBF /* CodeEditorPreferencesContainer.swift in Sources */, 62E98E207924FBDBAD355E57 /* WrappingNavigationViewController.swift in Sources */, 62E98B94B2E3FE4ABB80AE41 /* ProfileHeaderInfoView.swift in Sources */, + 2CE8390E20C8096400FE3672 /* ProfileAchievementsPresenter.swift in Sources */, 62E983C7E7B1194D9C809522 /* ProfileInfoPresenter.swift in Sources */, 62E983397736699787D8DD35 /* UIView+fromNib.swift in Sources */, 62E9851FBB1D8E51D5577726 /* StreakNotificationsControlPresenter.swift in Sources */, @@ -12058,6 +12254,18 @@ 62E981858E2E696693A1AB87 /* PinsMapBlockContentView.swift in Sources */, 62E9848BF3984E04C0061D6C /* ProfileViewController+StreakNotificationsControlView.swift in Sources */, 62E9888C86E6F2C4C8E8B89D /* ProfileMenuBlock.swift in Sources */, + 62E98176284407D3D2A0E271 /* Achievement.swift in Sources */, + 62E986991E9352C3266CAE49 /* AchievementProgress.swift in Sources */, + 62E98C73672CAA20DEB33CAC /* AchievementsAPI.swift in Sources */, + 62E98E72EB42F2A772BF9760 /* AchievementProgressesAPI.swift in Sources */, + 62E98A973A47105C195530CD /* AchievementsRetriever.swift in Sources */, + 62E98622F3B03F7282DC06D8 /* AchievementDescription.swift in Sources */, + 62E98381EC6AD8DD7613C32D /* AchievementsListViewController.swift in Sources */, + 62E98DE7FA0EE5EB23136B52 /* AchievementsListPresenter.swift in Sources */, + 62E98B8919D18CD0F8238F97 /* Collection+SafeSubscript.swift in Sources */, + 62E987CB113F6D20B108D070 /* AchievementPopupAlertManager.swift in Sources */, + 62E986E3352543F9ED07093F /* NotificationBannerType.swift in Sources */, + 62E98D686165FCA5E588FDD5 /* CustomNotificationBanner.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Stepic/Achievement.swift b/Stepic/Achievement.swift new file mode 100644 index 0000000000..ae06a7c98f --- /dev/null +++ b/Stepic/Achievement.swift @@ -0,0 +1,26 @@ +// +// Achievement.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 06.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// +import SwiftyJSON + +class Achievement: JSONSerializable { + var id: Int + var kind: String + var targetScore: Int + + required init(json: JSON) { + self.id = json["id"].intValue + self.kind = json["kind"].stringValue + self.targetScore = json["target_score"].intValue + } + + func update(json: JSON) { + self.id = json["id"].intValue + self.kind = json["kind"].stringValue + self.targetScore = json["target_score"].intValue + } +} diff --git a/Stepic/AchievementBadgeView.swift b/Stepic/AchievementBadgeView.swift new file mode 100644 index 0000000000..ba09d175c3 --- /dev/null +++ b/Stepic/AchievementBadgeView.swift @@ -0,0 +1,216 @@ +// +// AchievementBadgeView.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 04.06.2018. +// Copyright © 2018 Vladislav Kiryukhin. All rights reserved. +// + +import UIKit + +struct AchievementViewData { + let title: String + let description: String + + let badge: UIImage + + let completedLevel: Int + let maxLevel: Int + let score: Int + let maxScore: Int + + var isLocked: Bool { + return completedLevel == 0 + } +} + +class AchievementBadgeView: UIView { + // Gradient colors and locations for progress circle + private static let colors = [ + UIColor(hex: 0xa9aeff), + UIColor(hex: 0xa99cff), + UIColor(hex: 0xa992ff), + UIColor(hex: 0xaca5ff), + UIColor(hex: 0xacecfe) + ] + private static let locations = [0.0, 0.14, 0.25, 0.425, 1.0] + + private static let relativeBadgeHeight: CGFloat = 0.83 + private static let relativeStarsHeight: CGFloat = 0.09 + private static let relativeProgressWidth: CGFloat = 0.022 + private static let relativeBadgeImagePadding: CGFloat = 0.03 + + @IBOutlet weak var paddingConstraint: NSLayoutConstraint! + @IBOutlet weak var circleViewHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var starsStackViewHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var badgeImageViewHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var starsStackView: UIStackView! + @IBOutlet weak var circleView: UIView! + @IBOutlet weak var badgeImageView: UIImageView! + + var data: AchievementViewData? { + didSet { + updateProgress() + } + } + + var onTap: (() -> Void)? { + didSet { + self.isUserInteractionEnabled = onTap != nil + } + } + + var circleViewGradientLayer: CAGradientLayer? + var circleProgressLayer: CAShapeLayer? + private var previousBadgeFrame: CGRect? + + override func awakeFromNib() { + super.awakeFromNib() + + onTap = nil + let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.didTap)) + self.addGestureRecognizer(gestureRecognizer) + + clipsToBounds = true + + addGradient() + } + + @objc func didTap() { + onTap?() + } + + private func addGradient() { + circleViewGradientLayer = CAGradientLayer(colors: AchievementBadgeView.colors, locations: AchievementBadgeView.locations, rotationAngle: 130.0) + + guard let circleViewGradientLayer = circleViewGradientLayer else { + return + } + + circleViewGradientLayer.opacity = 0.25 + circleView.layer.insertSublayer(circleViewGradientLayer, at: 0) + } + + private func initViews() { + // Auto-resize: we calculate subviews sizes based on view height + let height = self.frame.height + let relativePaddingHeight = 1.0 - AchievementBadgeView.relativeBadgeHeight - AchievementBadgeView.relativeStarsHeight + let badgeHeight = AchievementBadgeView.relativeBadgeHeight * height + paddingConstraint.constant = relativePaddingHeight * height + circleViewHeightConstraint.constant = badgeHeight + starsStackViewHeightConstraint.constant = AchievementBadgeView.relativeStarsHeight * height + badgeImageViewHeightConstraint.constant = -2.0 * AchievementBadgeView.relativeBadgeImagePadding * badgeHeight + layoutIfNeeded() + + let progressWidth = height * AchievementBadgeView.relativeProgressWidth + let innerCircleRadius = badgeHeight * 0.5 + + // Draw gradient circle + let gradientCircleLayer = CAShapeLayer() + gradientCircleLayer.lineWidth = progressWidth + let bezierPath = UIBezierPath() + + bezierPath.addArc(withCenter: CGPoint(x: circleView.bounds.midX, y: circleView.bounds.midY), radius: innerCircleRadius - progressWidth, startAngle: 0, endAngle: 2 * .pi, clockwise: false) + gradientCircleLayer.path = bezierPath.cgPath + gradientCircleLayer.fillColor = nil + gradientCircleLayer.strokeColor = UIColor.black.cgColor + + circleViewGradientLayer?.mask = gradientCircleLayer + } + + private func initStageProgress(value: Float) { + let stageProgress = max(0.0, min(value, 1.0)) + + let height = self.frame.height + let badgeHeight = AchievementBadgeView.relativeBadgeHeight * height + let progressWidth = height * AchievementBadgeView.relativeProgressWidth + + let innerCircleRadius = badgeHeight * 0.5 + let progress = CGFloat(stageProgress) * 2.0 * .pi + + let circlePath = UIBezierPath() + circlePath.addArc(withCenter: CGPoint(x: circleView.bounds.midX, y: circleView.bounds.midY), radius: innerCircleRadius - progressWidth, startAngle: .pi / 2, endAngle: .pi / 2 + progress, clockwise: true) + + circleProgressLayer?.removeFromSuperlayer() + circleProgressLayer = CAShapeLayer() + + guard let circleProgressLayer = circleProgressLayer else { + return + } + + circleProgressLayer.path = circlePath.cgPath + circleProgressLayer.fillColor = nil + circleProgressLayer.strokeColor = UIColor.stepicGreen.cgColor + circleProgressLayer.lineWidth = progressWidth + + circleView.layer.addSublayer(circleProgressLayer) + } + + private func initLevelProgress(completedLevel: Int, maxLevel: Int) { + let completedLevel = max(min(maxLevel, completedLevel), 0) + + for v in starsStackView.arrangedSubviews { + starsStackView.removeArrangedSubview(v) + v.removeFromSuperview() + } + + if completedLevel != 0 { + // Remove previous width constraint (cause it based on maxLevel) + for c in starsStackView.constraints { + if c.firstAttribute == .width { + starsStackView.removeConstraint(c) + } + } + + let filledCount = completedLevel + let borderedCount = completedLevel == maxLevel ? 0 : 1 + let grayCount = maxLevel - filledCount - borderedCount + + let spaceBetweenStars = starsStackViewHeightConstraint.constant * 0.3 + starsStackView.spacing = spaceBetweenStars + + NSLayoutConstraint(item: starsStackView, attribute: .width, relatedBy: .equal, toItem: starsStackView, attribute: .height, multiplier: CGFloat(maxLevel), constant: CGFloat(maxLevel - 1) * spaceBetweenStars).isActive = true + + for _ in 0.. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/AchievementDescription.swift b/Stepic/AchievementDescription.swift new file mode 100644 index 0000000000..ef6d4d6a87 --- /dev/null +++ b/Stepic/AchievementDescription.swift @@ -0,0 +1,92 @@ +// +// AchievementDescription.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 12.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation + +enum AchievementKind: String { + // Cases should be declared in correct order, + // cause getBadge method uses hashValue + case stepsSolved = "steps_solved" + case stepsSolvedStreak = "steps_solved_streak" + case stepsSolvedChoice = "steps_solved_choice" + case stepsSolvedCode = "steps_solved_code" + case stepsSolvedNumber = "steps_solved_number" + case codeQuizzesSolvedPython = "code_quizzes_solved_python" + case codeQuizzesSolvedCPP = "code_quizzes_solved_cpp" + case codeQuizzesSolvedJava = "code_quizzes_solved_java" + case activeDaysStreak = "active_days_streak" + case certificatesRegularCount = "certificates_regular_count" + case certificatesDistinctionCount = "certificates_distinction_count" + case courseReviewsCount = "course_reviews_count" + + func getBadge(for level: Int) -> UIImage { + return UIImage(named: "achievement-\(self.hashValue + 1)-\(level)") ?? #imageLiteral(resourceName: "achievement-0") + } + + func getName() -> String { + switch self { + case .stepsSolved: + return NSLocalizedString("AchievementsStepsSolvedKindTitle", comment: "") + case .stepsSolvedChoice: + return NSLocalizedString("AchievementsStepsSolvedChoiceKindTitle", comment: "") + case .stepsSolvedCode: + return NSLocalizedString("AchievementsStepsSolvedCodeKindTitle", comment: "") + case .stepsSolvedNumber: + return NSLocalizedString("AchievementsStepsSolvedNumberKindTitle", comment: "") + case .codeQuizzesSolvedPython: + return NSLocalizedString("AchievementsCodeQuizzesSolvedPythonKindTitle", comment: "") + case .codeQuizzesSolvedJava: + return NSLocalizedString("AchievementsCodeQuizzesSolvedJavaKindTitle", comment: "") + case .codeQuizzesSolvedCPP: + return NSLocalizedString("AchievementsCodeQuizzesSolvedCppKindTitle", comment: "") + case .certificatesRegularCount: + return NSLocalizedString("AchievementsCertificatesRegularCountKindTitle", comment: "") + case .certificatesDistinctionCount: + return NSLocalizedString("AchievementsCertificatesDistinctionCountKindTitle", comment: "") + case .courseReviewsCount: + return NSLocalizedString("AchievementsCourseReviewsCountKindTitle", comment: "") + case .stepsSolvedStreak: + return NSLocalizedString("AchievementsStepsSolvedStreakKindTitle", comment: "") + case .activeDaysStreak: + return NSLocalizedString("AchievementsActiveDaysStreakKindTitle", comment: "") + default: + return NSLocalizedString("AchievementsUnknownKindTitle", comment: "") + } + } + + func getDescription(for score: Int) -> String { + switch self { + case .stepsSolved: + return String(format: NSLocalizedString("AchievementsStepsSolvedKindDescription", comment: ""), "\(score)") + case .stepsSolvedChoice: + return String(format: NSLocalizedString("AchievementsStepsSolvedChoiceKindDescription", comment: ""), "\(score)") + case .stepsSolvedCode: + return String(format: NSLocalizedString("AchievementsStepsSolvedCodeKindDescription", comment: ""), "\(score)") + case .stepsSolvedNumber: + return String(format: NSLocalizedString("AchievementsStepsSolvedNumberKindDescription", comment: ""), "\(score)") + case .codeQuizzesSolvedPython: + return String(format: NSLocalizedString("AchievementsCodeQuizzesSolvedPythonKindDescription", comment: ""), "\(score)") + case .codeQuizzesSolvedJava: + return String(format: NSLocalizedString("AchievementsCodeQuizzesSolvedJavaKindDescription", comment: ""), "\(score)") + case .codeQuizzesSolvedCPP: + return String(format: NSLocalizedString("AchievementsCodeQuizzesSolvedCppKindDescription", comment: ""), "\(score)") + case .certificatesRegularCount: + return String(format: NSLocalizedString("AchievementsCertificatesRegularCountKindDescription", comment: ""), "\(score)") + case .certificatesDistinctionCount: + return String(format: NSLocalizedString("AchievementsCertificatesDistinctionCountKindDescription", comment: ""), "\(score)") + case .courseReviewsCount: + return String(format: NSLocalizedString("AchievementsCourseReviewsCountKindDescription", comment: ""), "\(score)") + case .stepsSolvedStreak: + return String(format: NSLocalizedString("AchievementsStepsSolvedStreakKindDescription", comment: ""), "\(score)") + case .activeDaysStreak: + return String(format: NSLocalizedString("AchievementsActiveDaysStreakKindDescription", comment: ""), "\(score)") + default: + return NSLocalizedString("AchievementsUnknownKindDescription", comment: "") + } + } +} diff --git a/Stepic/AchievementListSkeletonPlaceholderView.xib b/Stepic/AchievementListSkeletonPlaceholderView.xib new file mode 100644 index 0000000000..dd44128e44 --- /dev/null +++ b/Stepic/AchievementListSkeletonPlaceholderView.xib @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/AchievementNotificationBannerView.swift b/Stepic/AchievementNotificationBannerView.swift new file mode 100644 index 0000000000..21f10e7604 --- /dev/null +++ b/Stepic/AchievementNotificationBannerView.swift @@ -0,0 +1,43 @@ +// +// AchievementNotificationBannerView.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 06.06.2018. +// Copyright © 2018 Vladislav Kiryukhin. All rights reserved. +// + +import UIKit + +class AchievementNotificationBannerView: UIView { + @IBOutlet weak var badgeImageView: UIImageView! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var descriptionLabel: UILabel! + @IBOutlet weak var badgeContainerView: UIView! + + var data: AchievementViewData? + + var containerViewGradient: CAGradientLayer = { + let gradientColors = [ + UIColor(hex: 0xa9aeff), + UIColor(hex: 0xa99cff), + UIColor(hex: 0xa992ff), + UIColor(hex: 0xaca5ff), + UIColor(hex: 0xacecfe) + ] + let gradientLocations = [0.0, 0.14, 0.25, 0.425, 1.0] + let gradient = CAGradientLayer(colors: gradientColors, locations: gradientLocations, rotationAngle: 130.0) + return gradient + }() + + override func layoutSubviews() { + super.layoutSubviews() + + if containerViewGradient.superlayer == nil { + badgeContainerView.layer.insertSublayer(containerViewGradient, at: 0) + + badgeImageView.image = data?.badge + } + + containerViewGradient.frame = badgeContainerView.bounds + } +} diff --git a/Stepic/AchievementNotificationBannerView.xib b/Stepic/AchievementNotificationBannerView.xib new file mode 100644 index 0000000000..be1df65772 --- /dev/null +++ b/Stepic/AchievementNotificationBannerView.xib @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/AchievementPopupAlertManager.swift b/Stepic/AchievementPopupAlertManager.swift new file mode 100644 index 0000000000..51ee66c801 --- /dev/null +++ b/Stepic/AchievementPopupAlertManager.swift @@ -0,0 +1,29 @@ +// +// AchievementPopupAlertManager.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 18.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import Presentr + +class AchievementPopupAlertManager: AlertManager { + func present(alert: UIViewController, inController controller: UIViewController) { + controller.customPresentViewController(presentr, viewController: alert, animated: true, completion: nil) + } + + let presentr: Presentr = { + let presentr = Presentr(presentationType: .dynamic(center: .center)) + presentr.roundCorners = true + return presentr + }() + + func construct(with data: AchievementViewData, canShare: Bool = false) -> AchievementPopupViewController { + let alert = AchievementPopupViewController(nibName: "AchievementPopupViewController", bundle: nil) + alert.data = data + alert.canShare = canShare + return alert + } +} diff --git a/Stepic/AchievementPopupViewController.swift b/Stepic/AchievementPopupViewController.swift new file mode 100644 index 0000000000..b1d6b2a58a --- /dev/null +++ b/Stepic/AchievementPopupViewController.swift @@ -0,0 +1,74 @@ +// +// AchievementPopupViewController.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 18.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +class AchievementPopupViewController: UIViewController { + @IBOutlet weak var achievementNameLabel: UILabel! + @IBOutlet weak var achievementDescriptionLabel: UILabel! + @IBOutlet weak var achievementBadgeImageView: UIImageView! + @IBOutlet weak var levelLabel: UILabel! + @IBOutlet weak var progressLabel: UILabel! + @IBOutlet weak var shareButton: StepikButton! + @IBOutlet weak var closeButton: UIButton! + + var widthConstraint: NSLayoutConstraint? + + var data: AchievementViewData? + var canShare: Bool = true + + @IBAction func onShareButtonClick(_ sender: Any) { + guard let data = data else { + return + } + + let activityVC = UIActivityViewController(activityItems: [String(format: NSLocalizedString("AchievementsShareText", comment: ""), "\(data.title)")], applicationActivities: nil) + activityVC.excludedActivityTypes = [UIActivityType.airDrop] + activityVC.popoverPresentationController?.sourceView = shareButton + present(activityVC, animated: true) + } + + @IBAction func onCloseButtonClick(_ sender: Any) { + self.dismiss(animated: true) + } + + override func viewDidLoad() { + super.viewDidLoad() + + achievementDescriptionLabel.constrainWidth("==\(UIScreen.main.bounds.width - 64)") + + shareButton.setTitle(NSLocalizedString("Share", comment: ""), for: .normal) + closeButton.setTitle(NSLocalizedString("Close", comment: ""), for: .normal) + + if let data = data { + update(with: data) + + if !canShare || data.isLocked { + shareButton.alpha = 0.0 + } + } + } + + private func update(with data: AchievementViewData) { + achievementNameLabel.text = data.title + achievementDescriptionLabel.text = data.description + achievementBadgeImageView.image = data.badge + + if data.completedLevel == data.maxLevel { + progressLabel.alpha = 0.0 + } else { + progressLabel.text = String(format: NSLocalizedString("AchievementsNextLevel", comment: ""), "\(data.maxScore - data.score)") + } + + if data.isLocked { + levelLabel.text = NSLocalizedString("AchievementsLevelNotObtained", comment: "") + } else { + levelLabel.text = String(format: NSLocalizedString("AchievementsLevel", comment: ""), "\(data.completedLevel)", "\(data.maxLevel)") + } + } +} diff --git a/Stepic/AchievementPopupViewController.xib b/Stepic/AchievementPopupViewController.xib new file mode 100644 index 0000000000..c52222e0c5 --- /dev/null +++ b/Stepic/AchievementPopupViewController.xib @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/AchievementProgress.swift b/Stepic/AchievementProgress.swift new file mode 100644 index 0000000000..d6a188af05 --- /dev/null +++ b/Stepic/AchievementProgress.swift @@ -0,0 +1,41 @@ +// +// AchievementProgress.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 06.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// +import SwiftyJSON + +class AchievementProgress: JSONSerializable { + var id: Int + var user: Int + var achievement: Int + var score: Int + var createDate: Date? + var updateDate: Date? + var obtainDate: Date? + var kind: String + + required init(json: JSON) { + self.id = json["id"].intValue + self.user = json["user"].intValue + self.achievement = json["achievement"].intValue + self.score = json["score"].intValue + self.kind = json["kind"].stringValue + self.createDate = Parser.sharedParser.dateFromTimedateJSON(json["create_date"]) + self.updateDate = Parser.sharedParser.dateFromTimedateJSON(json["update_date"]) + self.obtainDate = Parser.sharedParser.dateFromTimedateJSON(json["obtain_date"]) + } + + func update(json: JSON) { + self.id = json["id"].intValue + self.user = json["user"].intValue + self.achievement = json["achievement"].intValue + self.score = json["score"].intValue + self.kind = json["kind"].stringValue + self.createDate = Parser.sharedParser.dateFromTimedateJSON(json["create_date"]) + self.updateDate = Parser.sharedParser.dateFromTimedateJSON(json["update_date"]) + self.obtainDate = Parser.sharedParser.dateFromTimedateJSON(json["obtain_date"]) + } +} diff --git a/Stepic/AchievementProgressesAPI.swift b/Stepic/AchievementProgressesAPI.swift new file mode 100644 index 0000000000..fdacb1c4a3 --- /dev/null +++ b/Stepic/AchievementProgressesAPI.swift @@ -0,0 +1,35 @@ +// +// AchievementProgressesAPI.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 06.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit +import Alamofire + +class AchievementProgressesAPI: APIEndpoint { + override var name: String { return "achievement-progresses" } + + func retrieve(user: Int, kind: String? = nil, sortByObtainDateDesc: Bool = false, page: Int = 1) -> Promise<([AchievementProgress], Meta)> { + return Promise { fulfill, reject in + var params = Parameters() + if let kind = kind { + params["kind"] = kind + } + params["user"] = user + params["page"] = page + if sortByObtainDateDesc { + params["order"] = "-obtain_date" + } + + retrieve.request(requestEndpoint: name, paramName: name, params: params, updatingObjects: [], withManager: manager).then { progresses, meta -> Void in + fulfill((progresses, meta)) + }.catch { error in + reject(error) + } + } + } +} diff --git a/Stepic/AchievementSkeletonPlaceholderView.xib b/Stepic/AchievementSkeletonPlaceholderView.xib new file mode 100644 index 0000000000..257a39d364 --- /dev/null +++ b/Stepic/AchievementSkeletonPlaceholderView.xib @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/AchievementsAPI.swift b/Stepic/AchievementsAPI.swift new file mode 100644 index 0000000000..9d0bbc6813 --- /dev/null +++ b/Stepic/AchievementsAPI.swift @@ -0,0 +1,31 @@ +// +// AchievementsAPI.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 06.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit +import Alamofire + +class AchievementsAPI: APIEndpoint { + override var name: String { return "achievements" } + + func retrieve(kind: String? = nil, page: Int = 1) -> Promise<([Achievement], Meta)> { + return Promise { fulfill, reject in + var params = Parameters() + if let kind = kind { + params["kind"] = kind + } + params["page"] = page + + retrieve.request(requestEndpoint: name, paramName: name, params: params, updatingObjects: [], withManager: manager).then { achievements, meta -> Void in + fulfill((achievements, meta)) + }.catch { error in + reject(error) + } + } + } +} diff --git a/Stepic/AchievementsListPresenter.swift b/Stepic/AchievementsListPresenter.swift new file mode 100644 index 0000000000..ad794c90b2 --- /dev/null +++ b/Stepic/AchievementsListPresenter.swift @@ -0,0 +1,70 @@ +// +// AchievementsListPresenter.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 13.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// +import PromiseKit +import Foundation + +protocol AchievementsListView: class { + func set(achievements: [AchievementViewData]) + func showLoadingError() + func showAchievementInfo(viewData: AchievementViewData, canShare: Bool) +} + +class AchievementsListPresenter { + weak var view: AchievementsListView? + + private var achievementsAPI: AchievementsAPI + private var achievementsRetriever: AchievementsRetriever + private var userId: Int + + init(userId: Int, view: AchievementsListView, achievementsAPI: AchievementsAPI, achievementsRetriever: AchievementsRetriever) { + self.userId = userId + self.view = view + self.achievementsAPI = achievementsAPI + self.achievementsRetriever = achievementsRetriever + } + + func refresh() { + self.achievementsRetriever.loadAllAchievements(breakCondition: { _ in return false }).then { achievements -> Promise<[AchievementProgressData]> in + let kinds = Set(achievements.map { $0.kind }) + + var promises = [Promise]() + for kind in Array(kinds) { + promises.append(self.achievementsRetriever.loadAchievementProgress(for: kind)) + } + + return when(fulfilled: promises) + }.then { [weak self] progressData -> Void in + let viewData: [AchievementViewData] = progressData.compactMap { data in + guard let kindDescription = AchievementKind(rawValue: data.kind) else { + return nil + } + + return AchievementViewData(title: kindDescription.getName(), + description: kindDescription.getDescription(for: data.maxScore), + badge: kindDescription.getBadge(for: data.currentLevel), + completedLevel: data.currentLevel, + maxLevel: data.maxLevel, + score: data.currentScore, + maxScore: data.maxScore) + } + + self?.view?.set(achievements: viewData.sorted(by: { a, b in + let aScore = !a.isLocked ? 1 : (a.score > 0 ? 2 : 3) + let bScore = !b.isLocked ? 1 : (b.score > 0 ? 2 : 3) + return aScore < bScore + })) + }.catch { [weak self] error in + print("achievements list: error while loading = \(error)") + self?.view?.showLoadingError() + } + } + + func achievementSelected(with viewData: AchievementViewData) { + view?.showAchievementInfo(viewData: viewData, canShare: userId == AuthInfo.shared.userId) + } +} diff --git a/Stepic/AchievementsListTableViewCell.swift b/Stepic/AchievementsListTableViewCell.swift new file mode 100644 index 0000000000..aaf2397b68 --- /dev/null +++ b/Stepic/AchievementsListTableViewCell.swift @@ -0,0 +1,39 @@ +// +// AchievementsListTableViewCell.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 13.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit +import FLKAutoLayout + +class AchievementsListTableViewCell: UITableViewCell { + @IBOutlet weak var badgeContainer: UIView! + @IBOutlet weak var achievementName: UILabel! + @IBOutlet weak var achievementDescription: UILabel! + + private var badgeView: AchievementBadgeView? + + static let reuseId = "AchievementsListTableViewCell" + + func update(with viewData: AchievementViewData) { + achievementName.text = viewData.title + achievementDescription.text = viewData.description + + if badgeView == nil { + let badgeView: AchievementBadgeView = AchievementBadgeView.fromNib() + badgeView.translatesAutoresizingMaskIntoConstraints = false + badgeContainer.addSubview(badgeView) + badgeView.align(toView: badgeContainer) + self.badgeView = badgeView + } + + badgeView?.data = viewData + } + + override func prepareForReuse() { + super.prepareForReuse() + } +} diff --git a/Stepic/AchievementsListTableViewCell.xib b/Stepic/AchievementsListTableViewCell.xib new file mode 100644 index 0000000000..7e1657f190 --- /dev/null +++ b/Stepic/AchievementsListTableViewCell.xib @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/AchievementsListViewController.swift b/Stepic/AchievementsListViewController.swift new file mode 100644 index 0000000000..698e7534d7 --- /dev/null +++ b/Stepic/AchievementsListViewController.swift @@ -0,0 +1,84 @@ +// +// AchievementsListViewController.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 13.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import NotificationBannerSwift + +class AchievementsListViewController: UIViewController, AchievementsListView, ControllerWithStepikPlaceholder { + @IBOutlet weak var tableView: UITableView! + + var placeholderContainer = StepikPlaceholderControllerContainer() + + var presenter: AchievementsListPresenter? + private var data: [AchievementViewData] = [] + + override func viewDidLoad() { + super.viewDidLoad() + + title = NSLocalizedString("Achievements", comment: "") + + registerPlaceholder(placeholder: StepikPlaceholder(.noConnection, action: { [weak self] in + self?.refresh() + }), for: .connectionError) + + tableView.skeleton.viewBuilder = { return UIView.fromNib(named: "AchievementListSkeletonPlaceholderView") } + + tableView.register(UINib(nibName: "AchievementsListTableViewCell", bundle: nil), forCellReuseIdentifier: AchievementsListTableViewCell.reuseId) + + refresh() + } + + func set(achievements: [AchievementViewData]) { + data = achievements + + tableView.skeleton.hide() + tableView.reloadData() + } + + func showAchievementInfo(viewData: AchievementViewData, canShare: Bool) { + let alertManager = AchievementPopupAlertManager() + let vc = alertManager.construct(with: viewData, canShare: canShare) + alertManager.present(alert: vc, inController: self) + } + + func showLoadingError() { + showPlaceholder(for: .connectionError) + } + + private func refresh() { + isPlaceholderShown = false + tableView.skeleton.show() + presenter?.refresh() + } +} + +extension AchievementsListViewController: UITableViewDelegate, UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return data.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: AchievementsListTableViewCell.reuseId, for: indexPath) as? AchievementsListTableViewCell, + let viewData = data[safe: indexPath.row] else { + return UITableViewCell() + } + + cell.update(with: viewData) + return cell + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + guard let viewData = data[safe: indexPath.row] else { + return + } + + self.presenter?.achievementSelected(with: viewData) + } +} diff --git a/Stepic/AchievementsRetriever.swift b/Stepic/AchievementsRetriever.swift new file mode 100644 index 0000000000..5b7a7a5985 --- /dev/null +++ b/Stepic/AchievementsRetriever.swift @@ -0,0 +1,156 @@ +// +// AchievementsRetriever.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 08.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// +import PromiseKit +import Foundation + +class AchievementsRetriever { + private var userId: Int + + private var achievementsAPI: AchievementsAPI + private var achievementProgressesAPI: AchievementProgressesAPI + + init(userId: Int, achievementsAPI: AchievementsAPI, achievementProgressesAPI: AchievementProgressesAPI) { + self.userId = userId + self.achievementsAPI = achievementsAPI + self.achievementProgressesAPI = achievementProgressesAPI + } + + func loadAllAchievements(breakCondition: @escaping ([Achievement]) -> Bool) -> Promise<[Achievement]> { + var allAchievements = [Achievement]() + + func load(page: Int) -> Promise { + return Promise { fulfill, _ in + achievementsAPI.retrieve(page: page).then { (achievements, meta) -> Void in + allAchievements.append(contentsOf: achievements) + fulfill(meta.hasNext) + }.catch { _ in + fulfill(false) + } + } + } + + func collect(page: Int) -> Promise<[Achievement]> { + return load(page: page).then { hasNext -> Promise<[Achievement]> in + if !breakCondition(allAchievements) && hasNext { + return collect(page: page + 1) + } else { + return Promise(value: allAchievements) + } + } + } + + return collect(page: 1) + } + + func loadAllAchievementProgresses(breakCondition: @escaping ([AchievementProgress]) -> Bool) -> Promise<[AchievementProgress]> { + var allProgresses = [AchievementProgress]() + + func load(page: Int) -> Promise { + return Promise { fulfill, _ in + achievementProgressesAPI.retrieve(user: userId, sortByObtainDateDesc: true, page: page).then { (progresses, meta) -> Void in + allProgresses.append(contentsOf: progresses) + fulfill(meta.hasNext) + }.catch { _ in + fulfill(false) + } + } + } + + func collect(page: Int) -> Promise<[AchievementProgress]> { + return load(page: page).then { hasNext -> Promise<[AchievementProgress]> in + if !breakCondition(allProgresses) && hasNext { + return collect(page: page + 1) + } else { + return Promise(value: allProgresses) + } + } + } + + return collect(page: 1) + } + + func loadAchievementProgress(for achievement: Achievement) -> Promise { + return loadAchievementProgress(for: achievement.kind) + } + + func loadAchievementProgress(for kind: String) -> Promise { + return Promise { fulfill, reject in + let allAchievementsWithKind: Promise<[Achievement]> = Promise { fulfill, reject in + achievementsAPI.retrieve(kind: kind).then { achievements, _ -> Void in + fulfill(achievements) + }.catch { error in + reject(error) + } + } + let allProgressesWithKind: Promise<[AchievementProgress]> = Promise { fulfill, reject in + achievementProgressesAPI.retrieve(user: userId, kind: kind).then { progresses, _ -> Void in + fulfill(progresses) + }.catch { error in + reject(error) + } + } + + when(fulfilled: allAchievementsWithKind, allProgressesWithKind).then { (achievements, progresses) -> Void in + // achievement id -> target score + var idToTargetScore = [Int: Int]() + for achievement in achievements.sorted(by: { $0.targetScore < $1.targetScore }) { + idToTargetScore[achievement.id] = achievement.targetScore + } + + var levelCount = 0 + let progressesSortedByMaxScore = progresses.sorted(by: { a, b in + let lhs = idToTargetScore[a.achievement] ?? 0 + let rhs = idToTargetScore[b.achievement] ?? 0 + return lhs < rhs + }) + + // Sort achievements by progress and find first non-obtained + for progress in progressesSortedByMaxScore { + if progress.obtainDate == nil { + // Non-completed achievement, but have progress object + fulfill(AchievementProgressData(currentScore: progress.score, + maxScore: idToTargetScore[progress.achievement] ?? 0, + currentLevel: levelCount, + maxLevel: achievements.count, + kind: kind)) + return + } + levelCount += 1 + } + + // No non-obtained achievements were found + if let lastProgress = progressesSortedByMaxScore.last { + // Fulfilled achievement + fulfill(AchievementProgressData(currentScore: lastProgress.score, + maxScore: idToTargetScore[lastProgress.achievement] ?? 0, + currentLevel: achievements.count, + maxLevel: achievements.count, + kind: kind)) + } else { + let maxScoreForFirstLevel = achievements.sorted(by: { $0.targetScore < $1.targetScore }).first?.targetScore + // Non-completed achievement, empty progress + fulfill(AchievementProgressData(currentScore: 0, + maxScore: maxScoreForFirstLevel ?? 0, + currentLevel: 0, + maxLevel: achievements.count, + kind: kind)) + } + }.catch { error in + reject(error) + } + } + } +} + +struct AchievementProgressData { + var currentScore: Int + var maxScore: Int + var currentLevel: Int + var maxLevel: Int + var kind: String +} diff --git a/Stepic/CAGradientLayer+Init.swift b/Stepic/CAGradientLayer+Init.swift index ec6864dc9f..c2a0f657f8 100644 --- a/Stepic/CAGradientLayer+Init.swift +++ b/Stepic/CAGradientLayer+Init.swift @@ -9,9 +9,10 @@ import Foundation extension CAGradientLayer { - convenience init(colors: [UIColor], rotationAngle: CGFloat) { + convenience init(colors: [UIColor], locations: [Double]? = nil, rotationAngle: CGFloat) { self.init() self.colors = colors.map { $0.cgColor } + self.locations = locations as [NSNumber]? let angle: Float = Float(rotationAngle) / (2 * Float.pi) let startPointX = powf(sinf(2 * Float.pi * ((angle + 0.75) / 2)), 2) diff --git a/Stepic/Collection+SafeSubscript.swift b/Stepic/Collection+SafeSubscript.swift new file mode 100644 index 0000000000..e4ee3c83cd --- /dev/null +++ b/Stepic/Collection+SafeSubscript.swift @@ -0,0 +1,16 @@ +// +// Collection+SafeSubscript.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 14.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation + +extension Collection { + + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Stepic/ContentMenuBlockTableViewCell.swift b/Stepic/ContentMenuBlockTableViewCell.swift new file mode 100644 index 0000000000..eac96ec01b --- /dev/null +++ b/Stepic/ContentMenuBlockTableViewCell.swift @@ -0,0 +1,39 @@ +// +// ContentMenuBlockTableViewCell.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 06.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +class ContentMenuBlockTableViewCell: MenuBlockTableViewCell { + + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var actionButton: UIButton! + @IBOutlet weak var container: UIView! + + var onButtonClickAction: (() -> Void)? + + @IBAction func onActionButtonClick(_ sender: Any) { + onButtonClickAction?() + } + + override func initWithBlock(block: MenuBlock) { + super.initWithBlock(block: block) + titleLabel.text = block.title + + if let block = block as? ContentMenuBlock { + actionButton.setTitle(block.buttonTitle, for: .normal) + onButtonClickAction = block.onButtonClick + + if let contentView = block.contentView { + container.addSubview(contentView) + contentView.alignTop("0", leading: "0", bottom: "0", trailing: "0", toView: container) + layoutIfNeeded() + } + } + } + +} diff --git a/Stepic/ContentMenuBlockTableViewCell.xib b/Stepic/ContentMenuBlockTableViewCell.xib new file mode 100644 index 0000000000..ead5c84cee --- /dev/null +++ b/Stepic/ContentMenuBlockTableViewCell.xib @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/CustomNotificationBanner.swift b/Stepic/CustomNotificationBanner.swift new file mode 100644 index 0000000000..d4947ac89d --- /dev/null +++ b/Stepic/CustomNotificationBanner.swift @@ -0,0 +1,28 @@ +// +// CustomNotificationBanner.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 20.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import NotificationBannerSwift + +class CustomNotificationBanner: NotificationBanner { + private var banner: NotificationBannerSwift.NotificationBanner + private weak var view: UIView? + + init(view: UIView) { + self.view = view + self.banner = NotificationBannerSwift.NotificationBanner(customView: view) + } + + func show() { + self.banner.show() + } + + func dismiss() { + self.banner.dismiss() + } +} diff --git a/Stepic/Images.xcassets/Achievements/Badges/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-0.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-0.imageset/Contents.json new file mode 100644 index 0000000000..5fc4f64605 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-0.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-0.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-0.imageset/achievement-0.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-0.imageset/achievement-0.pdf new file mode 100644 index 0000000000..f859b52a61 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-0.imageset/achievement-0.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-1.imageset/Contents.json new file mode 100644 index 0000000000..f9e3fe2bbe --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-1-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-1.imageset/achievement-1-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-1.imageset/achievement-1-1.pdf new file mode 100644 index 0000000000..84c0b482a7 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-1.imageset/achievement-1-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-2.imageset/Contents.json new file mode 100644 index 0000000000..df21c6441b --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-1-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-2.imageset/achievement-1-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-2.imageset/achievement-1-2.pdf new file mode 100644 index 0000000000..fc81c48fa5 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-2.imageset/achievement-1-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-3.imageset/Contents.json new file mode 100644 index 0000000000..44edd7ceec --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-1-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-3.imageset/achievement-1-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-3.imageset/achievement-1-3.pdf new file mode 100644 index 0000000000..2d0224aec6 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-3.imageset/achievement-1-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-4.imageset/Contents.json new file mode 100644 index 0000000000..c7e6f7a023 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-1-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-4.imageset/achievement-1-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-4.imageset/achievement-1-4.pdf new file mode 100644 index 0000000000..91eaad838b Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-4.imageset/achievement-1-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-5.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-5.imageset/Contents.json new file mode 100644 index 0000000000..4d31bf7be0 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-5.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-1-5.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-5.imageset/achievement-1-5.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-5.imageset/achievement-1-5.pdf new file mode 100644 index 0000000000..86fda10c81 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-5.imageset/achievement-1-5.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-6.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-6.imageset/Contents.json new file mode 100644 index 0000000000..37a167a87b --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-6.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-1-6.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-1-6.imageset/achievement-1-6.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-6.imageset/achievement-1-6.pdf new file mode 100644 index 0000000000..11dad4678c Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-1-6.imageset/achievement-1-6.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-10-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-1.imageset/Contents.json new file mode 100644 index 0000000000..9f3f975030 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-10-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-10-1.imageset/achievement-10-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-1.imageset/achievement-10-1.pdf new file mode 100644 index 0000000000..1544645e3d Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-1.imageset/achievement-10-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-10-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-2.imageset/Contents.json new file mode 100644 index 0000000000..10d2efbd51 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-10-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-10-2.imageset/achievement-10-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-2.imageset/achievement-10-2.pdf new file mode 100644 index 0000000000..6c6db4216c Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-2.imageset/achievement-10-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-10-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-3.imageset/Contents.json new file mode 100644 index 0000000000..6c65beaed6 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-10-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-10-3.imageset/achievement-10-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-3.imageset/achievement-10-3.pdf new file mode 100644 index 0000000000..9b80780840 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-3.imageset/achievement-10-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-10-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-4.imageset/Contents.json new file mode 100644 index 0000000000..4aa61bb4ad --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-10-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-10-4.imageset/achievement-10-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-4.imageset/achievement-10-4.pdf new file mode 100644 index 0000000000..2da12ffcc9 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-10-4.imageset/achievement-10-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-11-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-1.imageset/Contents.json new file mode 100644 index 0000000000..72a750cce0 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-11-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-11-1.imageset/achievement-11-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-1.imageset/achievement-11-1.pdf new file mode 100644 index 0000000000..157d3031a4 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-1.imageset/achievement-11-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-11-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-2.imageset/Contents.json new file mode 100644 index 0000000000..3b84399ea1 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-11-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-11-2.imageset/achievement-11-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-2.imageset/achievement-11-2.pdf new file mode 100644 index 0000000000..5a516dc947 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-2.imageset/achievement-11-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-11-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-3.imageset/Contents.json new file mode 100644 index 0000000000..b44e34efca --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-11-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-11-3.imageset/achievement-11-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-3.imageset/achievement-11-3.pdf new file mode 100644 index 0000000000..094730c607 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-3.imageset/achievement-11-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-11-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-4.imageset/Contents.json new file mode 100644 index 0000000000..afb622fcaa --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-11-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-11-4.imageset/achievement-11-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-4.imageset/achievement-11-4.pdf new file mode 100644 index 0000000000..6895ba3002 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-11-4.imageset/achievement-11-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-12-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-1.imageset/Contents.json new file mode 100644 index 0000000000..7489defd6b --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-12-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-12-1.imageset/achievement-12-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-1.imageset/achievement-12-1.pdf new file mode 100644 index 0000000000..01c624197d Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-1.imageset/achievement-12-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-12-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-2.imageset/Contents.json new file mode 100644 index 0000000000..399f3b855b --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-12-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-12-2.imageset/achievement-12-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-2.imageset/achievement-12-2.pdf new file mode 100644 index 0000000000..067855a469 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-2.imageset/achievement-12-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-12-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-3.imageset/Contents.json new file mode 100644 index 0000000000..7b8eab9cdf --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-12-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-12-3.imageset/achievement-12-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-3.imageset/achievement-12-3.pdf new file mode 100644 index 0000000000..7545b61e3e Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-12-3.imageset/achievement-12-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-1.imageset/Contents.json new file mode 100644 index 0000000000..194fe4242f --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-2-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-1.imageset/achievement-2-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-1.imageset/achievement-2-1.pdf new file mode 100644 index 0000000000..4e64c68e18 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-1.imageset/achievement-2-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-2.imageset/Contents.json new file mode 100644 index 0000000000..3073c25703 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-2-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-2.imageset/achievement-2-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-2.imageset/achievement-2-2.pdf new file mode 100644 index 0000000000..89b48d0437 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-2.imageset/achievement-2-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-3.imageset/Contents.json new file mode 100644 index 0000000000..80207badac --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-2-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-3.imageset/achievement-2-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-3.imageset/achievement-2-3.pdf new file mode 100644 index 0000000000..13c63d6854 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-3.imageset/achievement-2-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-4.imageset/Contents.json new file mode 100644 index 0000000000..2022ce2573 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-2-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-4.imageset/achievement-2-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-4.imageset/achievement-2-4.pdf new file mode 100644 index 0000000000..db6004cb82 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-4.imageset/achievement-2-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-5.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-5.imageset/Contents.json new file mode 100644 index 0000000000..813012620b --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-5.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-2-5.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-5.imageset/achievement-2-5.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-5.imageset/achievement-2-5.pdf new file mode 100644 index 0000000000..9361845dc1 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-5.imageset/achievement-2-5.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-6.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-6.imageset/Contents.json new file mode 100644 index 0000000000..4d4dfe36b0 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-6.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-2-6.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-2-6.imageset/achievement-2-6.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-6.imageset/achievement-2-6.pdf new file mode 100644 index 0000000000..96315bcaf1 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-2-6.imageset/achievement-2-6.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-1.imageset/Contents.json new file mode 100644 index 0000000000..e3901b3062 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-3-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-1.imageset/achievement-3-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-1.imageset/achievement-3-1.pdf new file mode 100644 index 0000000000..7960f8666c Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-1.imageset/achievement-3-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-2.imageset/Contents.json new file mode 100644 index 0000000000..8f1463da5d --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-3-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-2.imageset/achievement-3-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-2.imageset/achievement-3-2.pdf new file mode 100644 index 0000000000..ad41e79600 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-2.imageset/achievement-3-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-3.imageset/Contents.json new file mode 100644 index 0000000000..89e9cf0d44 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-3-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-3.imageset/achievement-3-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-3.imageset/achievement-3-3.pdf new file mode 100644 index 0000000000..4ee23c13e6 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-3.imageset/achievement-3-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-4.imageset/Contents.json new file mode 100644 index 0000000000..61a50c6dbc --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-3-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-4.imageset/achievement-3-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-4.imageset/achievement-3-4.pdf new file mode 100644 index 0000000000..66c01c6457 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-4.imageset/achievement-3-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-5.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-5.imageset/Contents.json new file mode 100644 index 0000000000..1e69621a7e --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-5.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-3-5.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-5.imageset/achievement-3-5.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-5.imageset/achievement-3-5.pdf new file mode 100644 index 0000000000..d955b869e5 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-5.imageset/achievement-3-5.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-6.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-6.imageset/Contents.json new file mode 100644 index 0000000000..42fdc58c8f --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-6.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-3-6.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-3-6.imageset/achievement-3-6.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-6.imageset/achievement-3-6.pdf new file mode 100644 index 0000000000..9054853fe5 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-3-6.imageset/achievement-3-6.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-1.imageset/Contents.json new file mode 100644 index 0000000000..e853cd96e9 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-4-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-1.imageset/achievement-4-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-1.imageset/achievement-4-1.pdf new file mode 100644 index 0000000000..0ed041ef35 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-1.imageset/achievement-4-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-2.imageset/Contents.json new file mode 100644 index 0000000000..e054121281 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-4-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-2.imageset/achievement-4-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-2.imageset/achievement-4-2.pdf new file mode 100644 index 0000000000..9b5867fea6 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-2.imageset/achievement-4-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-3.imageset/Contents.json new file mode 100644 index 0000000000..9e703e871a --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-4-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-3.imageset/achievement-4-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-3.imageset/achievement-4-3.pdf new file mode 100644 index 0000000000..3f87ba9cdc Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-3.imageset/achievement-4-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-4.imageset/Contents.json new file mode 100644 index 0000000000..9878398b56 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-4-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-4.imageset/achievement-4-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-4.imageset/achievement-4-4.pdf new file mode 100644 index 0000000000..1f10854d43 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-4.imageset/achievement-4-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-5.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-5.imageset/Contents.json new file mode 100644 index 0000000000..3ae8e43a0c --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-5.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-4-5.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-5.imageset/achievement-4-5.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-5.imageset/achievement-4-5.pdf new file mode 100644 index 0000000000..ca54afa631 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-5.imageset/achievement-4-5.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-6.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-6.imageset/Contents.json new file mode 100644 index 0000000000..4aff4d8336 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-6.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-4-6.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-4-6.imageset/achievement-4-6.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-6.imageset/achievement-4-6.pdf new file mode 100644 index 0000000000..01f680b312 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-4-6.imageset/achievement-4-6.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-1.imageset/Contents.json new file mode 100644 index 0000000000..aaf5e83bcf --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-5-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-1.imageset/achievement-5-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-1.imageset/achievement-5-1.pdf new file mode 100644 index 0000000000..38f309a527 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-1.imageset/achievement-5-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-2.imageset/Contents.json new file mode 100644 index 0000000000..ae3dec916c --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-5-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-2.imageset/achievement-5-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-2.imageset/achievement-5-2.pdf new file mode 100644 index 0000000000..49128ffcd6 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-2.imageset/achievement-5-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-3.imageset/Contents.json new file mode 100644 index 0000000000..f08e40432e --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-5-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-3.imageset/achievement-5-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-3.imageset/achievement-5-3.pdf new file mode 100644 index 0000000000..bba1c53b5b Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-3.imageset/achievement-5-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-4.imageset/Contents.json new file mode 100644 index 0000000000..1d09406279 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-5-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-4.imageset/achievement-5-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-4.imageset/achievement-5-4.pdf new file mode 100644 index 0000000000..56c808e764 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-4.imageset/achievement-5-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-5.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-5.imageset/Contents.json new file mode 100644 index 0000000000..a79035d0be --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-5.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-5-5.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-5.imageset/achievement-5-5.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-5.imageset/achievement-5-5.pdf new file mode 100644 index 0000000000..c929d4bc8e Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-5.imageset/achievement-5-5.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-6.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-6.imageset/Contents.json new file mode 100644 index 0000000000..2c6847352d --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-6.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-5-6.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-5-6.imageset/achievement-5-6.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-6.imageset/achievement-5-6.pdf new file mode 100644 index 0000000000..309f018924 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-5-6.imageset/achievement-5-6.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-1.imageset/Contents.json new file mode 100644 index 0000000000..6d56998aba --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-6-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-1.imageset/achievement-6-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-1.imageset/achievement-6-1.pdf new file mode 100644 index 0000000000..dffbf5d75a Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-1.imageset/achievement-6-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-2.imageset/Contents.json new file mode 100644 index 0000000000..bdc30506ff --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-6-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-2.imageset/achievement-6-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-2.imageset/achievement-6-2.pdf new file mode 100644 index 0000000000..3941923dd0 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-2.imageset/achievement-6-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-3.imageset/Contents.json new file mode 100644 index 0000000000..04f6368c7c --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-6-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-3.imageset/achievement-6-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-3.imageset/achievement-6-3.pdf new file mode 100644 index 0000000000..371019e7f9 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-3.imageset/achievement-6-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-4.imageset/Contents.json new file mode 100644 index 0000000000..5628b80fb4 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-6-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-4.imageset/achievement-6-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-4.imageset/achievement-6-4.pdf new file mode 100644 index 0000000000..a1530edb9e Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-4.imageset/achievement-6-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-5.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-5.imageset/Contents.json new file mode 100644 index 0000000000..15267643df --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-5.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-6-5.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-5.imageset/achievement-6-5.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-5.imageset/achievement-6-5.pdf new file mode 100644 index 0000000000..0ec36e27ea Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-5.imageset/achievement-6-5.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-6.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-6.imageset/Contents.json new file mode 100644 index 0000000000..87c2a83e28 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-6.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-6-6.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-6-6.imageset/achievement-6-6.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-6.imageset/achievement-6-6.pdf new file mode 100644 index 0000000000..bc35fcb14e Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-6-6.imageset/achievement-6-6.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-1.imageset/Contents.json new file mode 100644 index 0000000000..dbd5c1f2ed --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-7-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-1.imageset/achievement-7-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-1.imageset/achievement-7-1.pdf new file mode 100644 index 0000000000..64a87357e9 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-1.imageset/achievement-7-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-2.imageset/Contents.json new file mode 100644 index 0000000000..899723b9c5 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-7-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-2.imageset/achievement-7-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-2.imageset/achievement-7-2.pdf new file mode 100644 index 0000000000..58b6f596c1 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-2.imageset/achievement-7-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-3.imageset/Contents.json new file mode 100644 index 0000000000..1e67fd0340 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-7-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-3.imageset/achievement-7-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-3.imageset/achievement-7-3.pdf new file mode 100644 index 0000000000..dbddefc99a Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-3.imageset/achievement-7-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-4.imageset/Contents.json new file mode 100644 index 0000000000..b82cbd6c26 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-7-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-4.imageset/achievement-7-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-4.imageset/achievement-7-4.pdf new file mode 100644 index 0000000000..fdfdaeff76 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-4.imageset/achievement-7-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-5.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-5.imageset/Contents.json new file mode 100644 index 0000000000..9b3d83736c --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-5.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-7-5.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-5.imageset/achievement-7-5.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-5.imageset/achievement-7-5.pdf new file mode 100644 index 0000000000..cf4c901ea0 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-5.imageset/achievement-7-5.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-6.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-6.imageset/Contents.json new file mode 100644 index 0000000000..66c5441972 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-6.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-7-6.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-7-6.imageset/achievement-7-6.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-6.imageset/achievement-7-6.pdf new file mode 100644 index 0000000000..2a25b840c4 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-7-6.imageset/achievement-7-6.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-1.imageset/Contents.json new file mode 100644 index 0000000000..8d8d807d55 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-8-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-1.imageset/achievement-8-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-1.imageset/achievement-8-1.pdf new file mode 100644 index 0000000000..03eb7a4488 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-1.imageset/achievement-8-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-2.imageset/Contents.json new file mode 100644 index 0000000000..0fc56071ad --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-8-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-2.imageset/achievement-8-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-2.imageset/achievement-8-2.pdf new file mode 100644 index 0000000000..b7828400e0 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-2.imageset/achievement-8-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-3.imageset/Contents.json new file mode 100644 index 0000000000..1b2ab86db2 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-8-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-3.imageset/achievement-8-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-3.imageset/achievement-8-3.pdf new file mode 100644 index 0000000000..a5c6e86bfe Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-3.imageset/achievement-8-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-4.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-4.imageset/Contents.json new file mode 100644 index 0000000000..5936cc4248 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-4.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-8-4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-4.imageset/achievement-8-4.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-4.imageset/achievement-8-4.pdf new file mode 100644 index 0000000000..9b01b40c9c Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-4.imageset/achievement-8-4.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-5.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-5.imageset/Contents.json new file mode 100644 index 0000000000..c4d88f252d --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-5.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-8-5.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-5.imageset/achievement-8-5.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-5.imageset/achievement-8-5.pdf new file mode 100644 index 0000000000..ba4d8fe50f Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-5.imageset/achievement-8-5.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-6.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-6.imageset/Contents.json new file mode 100644 index 0000000000..72969e9a22 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-6.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-8-6.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-8-6.imageset/achievement-8-6.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-6.imageset/achievement-8-6.pdf new file mode 100644 index 0000000000..8b872f8555 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-8-6.imageset/achievement-8-6.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-9-1.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-1.imageset/Contents.json new file mode 100644 index 0000000000..d36839af01 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-1.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-9-1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-9-1.imageset/achievement-9-1.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-1.imageset/achievement-9-1.pdf new file mode 100644 index 0000000000..fea0596c0d Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-1.imageset/achievement-9-1.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-9-2.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-2.imageset/Contents.json new file mode 100644 index 0000000000..36f7c1822b --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-2.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-9-2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-9-2.imageset/achievement-9-2.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-2.imageset/achievement-9-2.pdf new file mode 100644 index 0000000000..0eb472317e Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-2.imageset/achievement-9-2.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-9-3.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-3.imageset/Contents.json new file mode 100644 index 0000000000..b621c13119 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-3.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievement-9-3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/Badges/achievement-9-3.imageset/achievement-9-3.pdf b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-3.imageset/achievement-9-3.pdf new file mode 100644 index 0000000000..1d773ae339 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/Badges/achievement-9-3.imageset/achievement-9-3.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/Contents.json b/Stepic/Images.xcassets/Achievements/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/achievements-banner-arrow.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/achievements-banner-arrow.imageset/Contents.json new file mode 100644 index 0000000000..02ee8faa99 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/achievements-banner-arrow.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "achievements-banner-arrow.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/achievements-banner-arrow.imageset/achievements-banner-arrow.pdf b/Stepic/Images.xcassets/Achievements/achievements-banner-arrow.imageset/achievements-banner-arrow.pdf new file mode 100644 index 0000000000..3fcda72d5a Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/achievements-banner-arrow.imageset/achievements-banner-arrow.pdf differ diff --git a/Stepic/Images.xcassets/Achievements/star-bordered.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/star-bordered.imageset/Contents.json new file mode 100644 index 0000000000..6ded26d567 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/star-bordered.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "star-bordered.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "star-bordered@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "star-bordered@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered.png b/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered.png new file mode 100644 index 0000000000..b593518468 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered.png differ diff --git a/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered@2x.png b/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered@2x.png new file mode 100644 index 0000000000..389faad1fa Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered@2x.png differ diff --git a/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered@3x.png b/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered@3x.png new file mode 100644 index 0000000000..ffffbcd0f5 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-bordered.imageset/star-bordered@3x.png differ diff --git a/Stepic/Images.xcassets/Achievements/star-filled.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/star-filled.imageset/Contents.json new file mode 100644 index 0000000000..da512b96db --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/star-filled.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "star-filled.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "star-filled@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "star-filled@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled.png b/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled.png new file mode 100644 index 0000000000..9f740410eb Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled.png differ diff --git a/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled@2x.png b/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled@2x.png new file mode 100644 index 0000000000..e0e59783fb Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled@2x.png differ diff --git a/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled@3x.png b/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled@3x.png new file mode 100644 index 0000000000..b7c85c46a5 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-filled.imageset/star-filled@3x.png differ diff --git a/Stepic/Images.xcassets/Achievements/star-gray.imageset/Contents.json b/Stepic/Images.xcassets/Achievements/star-gray.imageset/Contents.json new file mode 100644 index 0000000000..9c7b271801 --- /dev/null +++ b/Stepic/Images.xcassets/Achievements/star-gray.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "star-gray.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "star-gray@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "star-gray@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray.png b/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray.png new file mode 100644 index 0000000000..61111ed0d8 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray.png differ diff --git a/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray@2x.png b/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray@2x.png new file mode 100644 index 0000000000..56dd28da86 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray@2x.png differ diff --git a/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray@3x.png b/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray@3x.png new file mode 100644 index 0000000000..1c6c5c0d38 Binary files /dev/null and b/Stepic/Images.xcassets/Achievements/star-gray.imageset/star-gray@3x.png differ diff --git a/Stepic/MenuBlocks.swift b/Stepic/MenuBlocks.swift index fb3fb619ce..699a9a8bd8 100644 --- a/Stepic/MenuBlocks.swift +++ b/Stepic/MenuBlocks.swift @@ -41,6 +41,19 @@ class ContentExpandableMenuBlock: ExpandableMenuBlock { } } +class ContentMenuBlock: MenuBlock { + weak var contentView: UIView? + var buttonTitle: String? + var onButtonClick: (() -> Void)? + + convenience init(id: String, title: String, contentView: UIView?, buttonTitle: String?, onButtonClick: (() -> Void)?) { + self.init(id: id, title: title) + self.contentView = contentView + self.buttonTitle = buttonTitle + self.onButtonClick = onButtonClick + } +} + class TransitionMenuBlock: MenuBlock { var subtitle: String? { didSet { diff --git a/Stepic/MenuUIManager.swift b/Stepic/MenuUIManager.swift index 2cecb06b8e..b252424e2e 100644 --- a/Stepic/MenuUIManager.swift +++ b/Stepic/MenuUIManager.swift @@ -41,6 +41,10 @@ class MenuUIManager { self?.tableView.endUpdates() } } + case .content: + if let cell = cell as? ContentMenuBlockTableViewCell, let block = block as? ContentMenuBlock { + cell.initWithBlock(block: block) + } case .header: if let cell = cell as? HeaderMenuBlockTableViewCell, let block = block as? HeaderMenuBlock { cell.initWithBlock(block: block) @@ -118,13 +122,15 @@ enum SupportedMenuBlockType { case contentExpandable case header case placeholder + case content static var all: [SupportedMenuBlockType] = [ .switchBlock, .transition, .contentExpandable, .header, - .placeholder + .placeholder, + .content ] var nibName: String { @@ -139,6 +145,8 @@ enum SupportedMenuBlockType { return "HeaderMenuBlockTableViewCell" case .placeholder: return "PlaceholderTableViewCell" + case .content: + return "ContentMenuBlockTableViewCell" } } @@ -167,6 +175,10 @@ enum SupportedMenuBlockType { self = .placeholder return } + if block is ContentMenuBlock { + self = .content + return + } return nil } } diff --git a/Stepic/NotificationBannerType.swift b/Stepic/NotificationBannerType.swift new file mode 100644 index 0000000000..ea7de6bb12 --- /dev/null +++ b/Stepic/NotificationBannerType.swift @@ -0,0 +1,28 @@ +// +// NotificationBannerType.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 20.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation + +protocol NotificationBanner { + func show() + func dismiss() +} + +enum NotificationBannerType { + case achievement(data: AchievementViewData) + + var banner: NotificationBanner { + switch self { + case .achievement(let data): + let view: AchievementNotificationBannerView = AchievementNotificationBannerView.fromNib() + view.translatesAutoresizingMaskIntoConstraints = false + view.data = data + return CustomNotificationBanner(view: view) + } + } +} diff --git a/Stepic/NotificationRequestAlertViewController.xib b/Stepic/NotificationRequestAlertViewController.xib index fc469ca5ae..8636be97b1 100644 --- a/Stepic/NotificationRequestAlertViewController.xib +++ b/Stepic/NotificationRequestAlertViewController.xib @@ -1,10 +1,11 @@ - + - + + diff --git a/Stepic/PlaceholderTableViewCell.swift b/Stepic/PlaceholderTableViewCell.swift index b59d6a3eca..7eaf1c3736 100644 --- a/Stepic/PlaceholderTableViewCell.swift +++ b/Stepic/PlaceholderTableViewCell.swift @@ -7,7 +7,6 @@ // import UIKit -import SkeletonView class PlaceholderTableViewCell: MenuBlockTableViewCell { @IBOutlet weak var placeholderView: UIView! @@ -15,14 +14,13 @@ class PlaceholderTableViewCell: MenuBlockTableViewCell { override func awakeFromNib() { super.awakeFromNib() - placeholderView.isSkeletonable = true let margin = CGFloat(arc4random()) / CGFloat(UInt32.max) * 20.0 - rightConstraint.constant = rightConstraint.constant + CGFloat(margin) + rightConstraint.constant = CGFloat(margin) } func startAnimating() { - placeholderView.showAnimatedGradientSkeleton(usingGradient: SkeletonGradient(baseColor: UIColor.mainLight), - animation: GradientDirection.leftRight.slidingAnimation()) + placeholderView.skeleton.viewBuilder = { return UIView.fromNib(named: "ProfileCellSkeletonPlaceholderView") } + placeholderView.skeleton.show() } } diff --git a/Stepic/PlaceholderTableViewCell.xib b/Stepic/PlaceholderTableViewCell.xib index 9f9683159e..5ece8dd6c9 100644 --- a/Stepic/PlaceholderTableViewCell.xib +++ b/Stepic/PlaceholderTableViewCell.xib @@ -1,5 +1,5 @@ - + @@ -20,24 +20,20 @@ - + - - - - + - - - + + + - diff --git a/Stepic/Profile.storyboard b/Stepic/Profile.storyboard index ed0a2b25a0..4ed84aff0e 100644 --- a/Stepic/Profile.storyboard +++ b/Stepic/Profile.storyboard @@ -1,5 +1,5 @@ - + @@ -209,5 +209,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/ProfileAchievementsContentView.swift b/Stepic/ProfileAchievementsContentView.swift new file mode 100644 index 0000000000..28c899d2ea --- /dev/null +++ b/Stepic/ProfileAchievementsContentView.swift @@ -0,0 +1,103 @@ +// +// ProfileAchievementsContentView.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 06.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit +import FLKAutoLayout + +class ProfileAchievementsContentView: UIView, ProfileAchievementsView { + @IBOutlet weak var achievementsStackView: UIStackView! + @IBOutlet weak var refreshButton: UIButton! + + private var presenter: ProfileAchievementsPresenter? + + private var achievementsCountInRow: Int { + if DeviceInfo.current.diagonal <= 4.0 { + return 3 + } + + if DeviceInfo.current.isPad || DeviceInfo.current.isPlus { + return 5 + } + + return 4 + } + + @IBAction func onRefreshButtonClick(_ sender: Any) { + refresh() + self.presenter?.loadLastAchievements() + } + + override func awakeFromNib() { + super.awakeFromNib() + + refreshButton.setTitle(NSLocalizedString("Refresh", comment: ""), for: .normal) + refreshButton.clipsToBounds = true + refreshButton.layer.cornerRadius = 8 + refreshButton.layer.borderWidth = 0.5 + refreshButton.layer.borderColor = UIColor(red: 204 / 255, green: 204 / 255, blue: 204 / 255, alpha: 1.0).cgColor + + refreshButton.contentEdgeInsets = UIEdgeInsets(top: 12.0, left: 23.0, bottom: 12.0, right: 23.0) + refreshButton.setTitleColor(UIColor(red: 83 / 255, green: 83 / 255, blue: 102 / 255, alpha: 1.0), for: .normal) + + achievementsStackView?.alpha = 0.0 + + refresh() + } + + private func addPlaceholdersView() { + for v in achievementsStackView.arrangedSubviews { + achievementsStackView.removeArrangedSubview(v) + v.removeFromSuperview() + } + + for _ in 0.. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/ProfileAchievementsPresenter.swift b/Stepic/ProfileAchievementsPresenter.swift new file mode 100644 index 0000000000..93e8840326 --- /dev/null +++ b/Stepic/ProfileAchievementsPresenter.swift @@ -0,0 +1,130 @@ +// +// ProfileAchievementsPresenter.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 06.06.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation +import PromiseKit + +protocol ProfileAchievementsView: class { + func set(achievements: [AchievementViewData]) + func showLoadingError() + func attachPresenter(_ presenter: ProfileAchievementsPresenter) +} + +protocol ProfileAchievementsPresenterDelegate: class { + func achievementInfoShouldPresent(viewData: AchievementViewData) +} + +class ProfileAchievementsPresenter { + weak var view: ProfileAchievementsView? + weak var delegate: ProfileAchievementsPresenterDelegate? + + private static let maxProfileAchievementsCount = 5 + + private var userId: Int + private var achievementsAPI: AchievementsAPI + private var achievementProgressesAPI: AchievementProgressesAPI + private var achievementsRetriever: AchievementsRetriever + + init(userId: Int, view: ProfileAchievementsView, achievementsAPI: AchievementsAPI, achievementProgressesAPI: AchievementProgressesAPI) { + self.view = view + self.userId = userId + self.achievementsAPI = achievementsAPI + self.achievementProgressesAPI = achievementProgressesAPI + + self.achievementsRetriever = AchievementsRetriever(userId: userId, + achievementsAPI: achievementsAPI, + achievementProgressesAPI: achievementProgressesAPI) + } + + func loadLastAchievements() { + // kind -> isObtained + var allUniqueKinds = [String: Bool]() + + // Load achievements while we have less kinds than maxProfileAchievementsCount (+kinds from allUniqueKinds) + let achievementsBreakCondition: ([Achievement]) -> Bool = { achievements -> Bool in + // kind -> isObtained + var uniqueKinds = Set() + + for a in achievements { + if allUniqueKinds[a.kind] == nil { + uniqueKinds.insert(a.kind) + } + } + + return allUniqueKinds.count + uniqueKinds.count >= ProfileAchievementsPresenter.maxProfileAchievementsCount + } + + // Load progresses while we have less unique kinds than maxProfileAchievementsCount + let progressesBreakCondition: ([AchievementProgress]) -> Bool = { progresses -> Bool in + // kind -> isObtained + var uniqueKinds = [String: Bool]() + + for p in progresses { + uniqueKinds[p.kind] = (uniqueKinds[p.kind] ?? false) || (p.obtainDate != nil) + } + + return uniqueKinds.count >= ProfileAchievementsPresenter.maxProfileAchievementsCount + } + + func extractMoreKinds() -> Promise<[String]> { + return self.achievementsRetriever.loadAllAchievements(breakCondition: achievementsBreakCondition).then { allAchievements -> Promise<[String]> in + for a in allAchievements { + allUniqueKinds[a.kind] = false + if allUniqueKinds.count >= ProfileAchievementsPresenter.maxProfileAchievementsCount { + break + } + } + + let kinds = allUniqueKinds.map { k, v in (k, v) } + return Promise(value: kinds.sorted(by: { $0.1 && !$1.1 }).map { $0.0 }) + } + } + + self.achievementsRetriever.loadAllAchievementProgresses(breakCondition: progressesBreakCondition).then { allProgresses -> Promise<[String]> in + for p in allProgresses { + allUniqueKinds[p.kind] = (allUniqueKinds[p.kind] ?? false) || (p.obtainDate != nil) + } + + if allUniqueKinds.count < ProfileAchievementsPresenter.maxProfileAchievementsCount { + // We should load more achievements with unknown progress + return extractMoreKinds() + } else { + let kinds = allUniqueKinds.map { k, v in (k, v) } + return Promise(value: kinds.sorted(by: { $0.1 && !$1.1 }).map { $0.0 }) + } + }.then { kinds -> Promise<[AchievementProgressData]> in + let promises = kinds.compactMap { [weak self] kind in + self?.achievementsRetriever.loadAchievementProgress(for: kind) + } + + return when(fulfilled: promises) + }.then { [weak self] progressData -> Void in + let viewData: [AchievementViewData] = progressData.compactMap { data in + guard let kindDescription = AchievementKind(rawValue: data.kind) else { + return nil + } + + return AchievementViewData(title: kindDescription.getName(), + description: kindDescription.getDescription(for: data.maxScore), + badge: kindDescription.getBadge(for: data.currentLevel), + completedLevel: data.currentLevel, + maxLevel: data.maxLevel, + score: data.currentScore, + maxScore: data.maxScore) + } + self?.view?.set(achievements: viewData) + }.catch { [weak self] error in + print("profile achievements: error while loading = \(error)") + self?.view?.showLoadingError() + } + } + + func openAchievementInfo(with data: AchievementViewData) { + delegate?.achievementInfoShouldPresent(viewData: data) + } +} diff --git a/Stepic/ProfileCellSkeletonPlaceholderView.xib b/Stepic/ProfileCellSkeletonPlaceholderView.xib new file mode 100644 index 0000000000..305d0aaa21 --- /dev/null +++ b/Stepic/ProfileCellSkeletonPlaceholderView.xib @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stepic/ProfileMenuBlock.swift b/Stepic/ProfileMenuBlock.swift index bdfbb4d55b..06e1ad06b3 100644 --- a/Stepic/ProfileMenuBlock.swift +++ b/Stepic/ProfileMenuBlock.swift @@ -14,6 +14,7 @@ enum ProfileMenuBlock: RawRepresentable, Equatable { case notificationsTimeSelection case description case pinsMap + case achievements init?(rawValue: RawValue) { fatalError("init with raw value has not been implemented") @@ -31,6 +32,8 @@ enum ProfileMenuBlock: RawRepresentable, Equatable { return "description" case .pinsMap: return "pinsMap" + case .achievements: + return "achievements" } } diff --git a/Stepic/ProfilePresenter.swift b/Stepic/ProfilePresenter.swift index b65bd71226..ab55e49a2a 100644 --- a/Stepic/ProfilePresenter.swift +++ b/Stepic/ProfilePresenter.swift @@ -14,6 +14,7 @@ protocol ProfileView: class { func requestNotificationsPermissions() func showStreakTimeSelection(startHour: Int) + func showAchievementInfo(viewData: AchievementViewData, canShare: Bool) func getView(for block: ProfileMenuBlock) -> Any? func setMenu(blocks: [ProfileMenuBlock]) @@ -45,6 +46,7 @@ class ProfilePresenter { private var streakNotificationsPresenter: StreakNotificationsControlPresenter? private var descriptionPresenter: ProfileDescriptionPresenter? private var pinsMapPresenter: PinsMapPresenter? + private var achievementsPresenter: ProfileAchievementsPresenter? private var userActivitiesAPI: UserActivitiesAPI private var usersAPI: UsersAPI @@ -55,8 +57,9 @@ class ProfilePresenter { private static let selfUserMenu: [ProfileMenuBlock] = [.infoHeader, .notificationsSwitch(isOn: false), .pinsMap, + .achievements, .description] - private static let otherUserMenu: [ProfileMenuBlock] = [.infoHeader, .pinsMap, .description] + private static let otherUserMenu: [ProfileMenuBlock] = [.infoHeader, .pinsMap, .achievements, .description] init(userSeed: UserSeed, view: ProfileView, userActivitiesAPI: UserActivitiesAPI, usersAPI: UsersAPI, notificationPermissionManager: NotificationPermissionManager) { self.view = view @@ -93,6 +96,20 @@ class ProfilePresenter { pinsMapPresenter = PinsMapPresenter(view: attachedView) } + // Achievements + if let attachedView = view?.getView(for: .achievements) as? ProfileAchievementsView, + let userId = userSeed.userId { + achievementsPresenter = ProfileAchievementsPresenter(userId: userId, + view: attachedView, + achievementsAPI: AchievementsAPI(), + achievementProgressesAPI: AchievementProgressesAPI()) + if let achievementsPresenter = achievementsPresenter { + attachedView.attachPresenter(achievementsPresenter) + achievementsPresenter.delegate = self + achievementsPresenter.loadLastAchievements() + } + } + refreshUser(with: user) refreshStreak(with: activity) headerInfoPresenter?.hideLoading() @@ -207,3 +224,9 @@ class ProfilePresenter { case noProfile } } + +extension ProfilePresenter: ProfileAchievementsPresenterDelegate { + func achievementInfoShouldPresent(viewData: AchievementViewData) { + view?.showAchievementInfo(viewData: viewData, canShare: userSeed.isMe) + } +} diff --git a/Stepic/ProfileViewController.swift b/Stepic/ProfileViewController.swift index 0c75f2b83d..38bc29f092 100644 --- a/Stepic/ProfileViewController.swift +++ b/Stepic/ProfileViewController.swift @@ -16,6 +16,7 @@ class ProfileViewController: MenuViewController, ProfileView, ControllerWithStep var profileStreaksView: ProfileHeaderInfoView? var profileDescriptionView: ProfileDescriptionContentView? var pinsMapContentView: PinsMapBlockContentView? + var profileAchievementsView: ProfileAchievementsContentView? // Implementation of StreakNotificationsControlView in extension var presenterNotifications: StreakNotificationsControlPresenter? @@ -117,6 +118,8 @@ class ProfileViewController: MenuViewController, ProfileView, ControllerWithStep menuBlocks.append(buildInfoExpandableBlock()) case .pinsMap: menuBlocks.append(buildPinsMapExpandableBlock()) + case .achievements: + menuBlocks.append(buildAchievementsBlock()) default: break } @@ -142,9 +145,17 @@ class ProfileViewController: MenuViewController, ProfileView, ControllerWithStep return self.profileDescriptionView case .pinsMap: return self.pinsMapContentView + case .achievements: + return self.profileAchievementsView } } + func showAchievementInfo(viewData: AchievementViewData, canShare: Bool) { + let alertManager = AchievementPopupAlertManager() + let vc = alertManager.construct(with: viewData, canShare: canShare) + alertManager.present(alert: vc, inController: self) + } + private func initPresenter() { // Init only with other/anonymous seed // Presenter check anonymous seed and load self profile if we have logged user @@ -292,6 +303,27 @@ class ProfileViewController: MenuViewController, ProfileView, ControllerWithStep } return block } + + private func buildAchievementsBlock() -> ContentMenuBlock? { + profileAchievementsView = profileAchievementsView ?? ProfileAchievementsContentView.fromNib() + let onButtonClick = { [weak self] in + if let userId = self?.otherUserId ?? AuthInfo.shared.userId, + let vc = ControllerHelper.instantiateViewController(identifier: "AchievementsListViewController", storyboardName: "Profile") as? AchievementsListViewController { + // FIXME: API injection :(( + let retriever = AchievementsRetriever(userId: userId, achievementsAPI: AchievementsAPI(), achievementProgressesAPI: AchievementProgressesAPI()) + let presenter = AchievementsListPresenter(userId: userId, view: vc, achievementsAPI: AchievementsAPI(), achievementsRetriever: retriever) + vc.presenter = presenter + self?.navigationController?.pushViewController(vc, animated: true) + } + } + + let block = ContentMenuBlock(id: ProfileMenuBlock.achievements.rawValue, + title: NSLocalizedString("Achievements", comment: ""), + contentView: profileAchievementsView, + buttonTitle: NSLocalizedString("ShowAll", comment: ""), + onButtonClick: onButtonClick) + return block + } } enum ProfileState { diff --git a/Stepic/SkeletonView.swift b/Stepic/SkeletonView.swift new file mode 100644 index 0000000000..ef3da65bbe --- /dev/null +++ b/Stepic/SkeletonView.swift @@ -0,0 +1,161 @@ +// +// SkeletonView.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 22.06.2018. +// Copyright © 2018 Vladislav Kiryukhin. All rights reserved. +// + +import UIKit + +class SkeletonView: UIView { + private static let maxSubviewDepth = 3 + + private var cachedViews = [UIView]() + private var shouldRebuildCache = true + + private var placeholderView: UIView? { + didSet { + rebuild(cleanCache: true) + } + } + + private var gradientLayer: CAGradientLayer? + + convenience init(placeholderView: UIView) { + self.init() + + self.translatesAutoresizingMaskIntoConstraints = false + placeholderView.translatesAutoresizingMaskIntoConstraints = false + + // For debug + placeholderView.accessibilityIdentifier = "placeholderView" + self.accessibilityIdentifier = "skeletonView" + + // We should add and hide placeholder view to get new layout in layoutSubviews() + addSubview(placeholderView) + placeholderView.alpha = 0.0 + placeholderView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true + placeholderView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true + placeholderView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true + placeholderView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true + + self.placeholderView = placeholderView + + addGradient() + } + + func show(in view: UIView) { + if self.superview != nil && self.superview != view { + self.removeFromSuperview() + } + + self.tag = -1 + self.backgroundColor = .white + view.addSubview(self) + + self.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true + self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true + self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true + } + + func hide() { + self.removeFromSuperview() + } + + private func addGradient() { + let gradientLayer = CAGradientLayer() + // FIXME: extract colors + gradientLayer.colors = [ + UIColor(red: 0.965, green: 0.965, blue: 0.965, alpha: 1.0).cgColor, + UIColor(red: 0.907, green: 0.907, blue: 0.907, alpha: 1.0).cgColor, + UIColor(red: 0.965, green: 0.965, blue: 0.965, alpha: 1.0).cgColor + ] + + self.layer.addSublayer(gradientLayer) + self.gradientLayer = gradientLayer + } + + private func rebuild(cleanCache: Bool) { + guard let view = placeholderView else { + return + } + + if cleanCache || shouldRebuildCache { + cachedViews.removeAll() + cachedViews = traverseSubviews(view: view, maxDepth: SkeletonView.maxSubviewDepth) + shouldRebuildCache = false + } + + let mask = buildMaskLayer(with: cachedViews) + gradientLayer?.mask = mask + } + + private func convertFrame(for view: UIView) -> CGRect? { + guard let mainSuperview = self.superview, + let superview = view.superview else { + return nil + } + + return superview.convert(view.frame, to: mainSuperview) + } + + private func buildMaskLayer(with views: [UIView]) -> CAShapeLayer { + let mutablePath = CGMutablePath() + for view in views { + if let convertedFrame = convertFrame(for: view) { + // Strange Apple's assertions in CoreGraphics: + // (corner_width >= 0 && 2 * corner_width <= CGRectGetWidth(rect)) + // (corner_height >= 0 && 2 * corner_width <= CGRectGetHeight(rect)) + // Appears when condition is true, some workaround to fix them + let cornerWidth = max(0.0, min(view.layer.cornerRadius, convertedFrame.width * 0.5 - 1e-3)) + let cornerHeight = max(0.0, min(view.layer.cornerRadius, convertedFrame.height * 0.5 - 1e-3)) + mutablePath.addRoundedRect(in: convertedFrame, cornerWidth: cornerWidth, cornerHeight: cornerHeight) + } + } + mutablePath.closeSubpath() + + let maskLayer = CAShapeLayer() + maskLayer.path = mutablePath + + return maskLayer + } + + private func traverseSubviews(view: UIView, maxDepth: Int) -> [UIView] { + func traverse(view: UIView, depth: Int) -> [UIView] { + if view.tag == -1 { + return [] + } + + if depth == maxDepth || view.subviews.count == 0 { + return [view] + } + + var views = [UIView]() + for subview in view.subviews { + views.append(contentsOf: traverse(view: subview, depth: depth + 1)) + } + return views + } + + return traverse(view: view, depth: 0) + } + + private func animate() { + let animationKey = "skeletonView" + + self.gradientLayer?.removeAnimation(forKey: animationKey) + self.gradientLayer?.add(SkeletonViewAnimation.sliding.animation, forKey: animationKey) + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.setNeedsLayout() + + rebuild(cleanCache: false) + gradientLayer?.frame = self.bounds + animate() + } +} diff --git a/Stepic/SkeletonViewAnimation.swift b/Stepic/SkeletonViewAnimation.swift new file mode 100644 index 0000000000..99346d480a --- /dev/null +++ b/Stepic/SkeletonViewAnimation.swift @@ -0,0 +1,37 @@ +// +// SkeletonViewAnimation.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 25.06.2018. +// Copyright © 2018 Vladislav Kiryukhin. All rights reserved. +// + +import UIKit + +// For static and pulsation effects in the future +// TODO: extract colors +enum SkeletonViewAnimation { + case sliding + + var animation: CAAnimation { + switch self { + case .sliding: + let startPoint = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.startPoint)) + startPoint.fromValue = CGPoint(x: -1.0, y: 0.5) + startPoint.toValue = CGPoint(x: 1.0, y: 0.5) + + let endPoint = CABasicAnimation(keyPath: #keyPath(CAGradientLayer.endPoint)) + endPoint.fromValue = CGPoint(x: 0.0, y: 0.5) + endPoint.toValue = CGPoint(x: 2.0, y: 0.5) + + let animationGroup = CAAnimationGroup() + animationGroup.animations = [startPoint, endPoint] + animationGroup.duration = 1.5 + animationGroup.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) + animationGroup.repeatCount = .infinity + animationGroup.isRemovedOnCompletion = false + + return animationGroup + } + } +} diff --git a/Stepic/Skeletonable+UITableView.swift b/Stepic/Skeletonable+UITableView.swift new file mode 100644 index 0000000000..f180c08629 --- /dev/null +++ b/Stepic/Skeletonable+UITableView.swift @@ -0,0 +1,89 @@ +// +// Skeletonable+UITableView.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 25.06.2018. +// Copyright © 2018 Vladislav Kiryukhin. All rights reserved. +// + +import UIKit + +private struct AssociatedKey { + static var savedDataSource = "savedDataSource" + static var skeletonDataSource = "skeletonDataSource" +} + +extension UITableView { + private var savedDataSource: UITableViewDataSource? { + get { + return objc_getAssociatedObject(self, &AssociatedKey.savedDataSource) as? UITableViewDataSource + } + set { + objc_setAssociatedObject(self, &AssociatedKey.savedDataSource, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + private var skeletonDataSource: SkeletonTableViewDataSource? { + get { + return objc_getAssociatedObject(self, &AssociatedKey.skeletonDataSource) as? SkeletonTableViewDataSource + } + set { + objc_setAssociatedObject(self, &AssociatedKey.skeletonDataSource, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + override func showSkeleton() { + self.register(SkeletonTableViewCell.self, forCellReuseIdentifier: SkeletonTableViewCell.reuseId) + + savedDataSource = self.dataSource + skeletonDataSource = SkeletonTableViewDataSource() + + self.dataSource = skeletonDataSource + self.reloadData() + + self.isUserInteractionEnabled = false + } + + override func hideSkeleton() { + self.dataSource = savedDataSource + self.reloadData() + self.isUserInteractionEnabled = true + + skeletonDataSource = nil + } +} + +class SkeletonTableViewCell: UITableViewCell { + static let reuseId = "SkeletonTableViewCell" + + func attach(view: SkeletonView) { + view.show(in: contentView) + } +} + +class SkeletonTableViewDataSource: NSObject, UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return Int(max(tableView.bounds.width, tableView.bounds.height) / 44.0) + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: SkeletonTableViewCell.reuseId, for: indexPath) as? SkeletonTableViewCell else { + return UITableViewCell() + } + + guard let placeholderCellView = tableView.skeleton.viewBuilder() else { + return UITableViewCell() + } + + cell.attach(view: SkeletonView(placeholderView: placeholderCellView)) + return cell + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return UITableViewAutomaticDimension + } +} diff --git a/Stepic/Skeletonable+UIView.swift b/Stepic/Skeletonable+UIView.swift new file mode 100644 index 0000000000..0fe462a6d3 --- /dev/null +++ b/Stepic/Skeletonable+UIView.swift @@ -0,0 +1,37 @@ +// +// Skeletonable+UIView.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 25.06.2018. +// Copyright © 2018 Vladislav Kiryukhin. All rights reserved. +// + +import UIKit + +private struct AssociatedKey { + static var skeletonView = "skeletonView" +} + +extension UIView: Skeletonable { + private var skeletonView: SkeletonView? { + get { + return objc_getAssociatedObject(self, &AssociatedKey.skeletonView) as? SkeletonView + } + set { + objc_setAssociatedObject(self, &AssociatedKey.skeletonView, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + + func showSkeleton() { + guard let placeholderView = self.skeleton.viewBuilder() else { + return + } + + skeletonView = SkeletonView(placeholderView: placeholderView) + skeletonView?.show(in: self) + } + + func hideSkeleton() { + skeletonView?.hide() + } +} diff --git a/Stepic/Skeletonable.swift b/Stepic/Skeletonable.swift new file mode 100644 index 0000000000..c7021133c0 --- /dev/null +++ b/Stepic/Skeletonable.swift @@ -0,0 +1,51 @@ +// +// Skeletonable.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 22.06.2018. +// Copyright © 2018 Vladislav Kiryukhin. All rights reserved. +// + +import UIKit + +private struct AssociatedKey { + static var skeleton = "skeleton" +} + +// Proxy class to hide UIView implementation +class Skeleton { + private var parent: Skeletonable + + var viewBuilder: (() -> UIView?) = { + return nil + } + + init(parent: Skeletonable) { + self.parent = parent + } + + func show() { + parent.showSkeleton() + } + + func hide() { + parent.hideSkeleton() + } +} + +@objc protocol Skeletonable { + @objc func showSkeleton() + @objc func hideSkeleton() +} + +extension Skeletonable { + var skeleton: Skeleton { + if let skeleton = objc_getAssociatedObject(self, &AssociatedKey.skeleton) as? Skeleton { + return skeleton + } + + let skeleton = Skeleton(parent: self) + objc_setAssociatedObject(self, &AssociatedKey.skeleton, skeleton, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + return skeleton + } +} diff --git a/Stepic/StepikPlaceholderView.xib b/Stepic/StepikPlaceholderView.xib index 33f3f69ef2..bd36ae0da9 100644 --- a/Stepic/StepikPlaceholderView.xib +++ b/Stepic/StepikPlaceholderView.xib @@ -1,10 +1,11 @@ - + - + + diff --git a/Stepic/UIView+fromNib.swift b/Stepic/UIView+fromNib.swift index 51bbfbbb58..c7f69ac7f2 100644 --- a/Stepic/UIView+fromNib.swift +++ b/Stepic/UIView+fromNib.swift @@ -11,4 +11,8 @@ extension UIView { class func fromNib() -> T { return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)![0] as! T } + + class func fromNib(named: String) -> UIView { + return Bundle.main.loadNibNamed(named, owner: nil, options: nil)![0] as! UIView + } } diff --git a/Stepic/en.lproj/Localizable.strings b/Stepic/en.lproj/Localizable.strings index ea455e38f2..85289f97b5 100644 --- a/Stepic/en.lproj/Localizable.strings +++ b/Stepic/en.lproj/Localizable.strings @@ -336,6 +336,7 @@ EnterSQLQuery = "Enter SQL query:"; SolveStreaksDaysCount = "Your current streak is %@ %@ in a row. Great!"; SolveSomethingToday = "Solve anything today to increase your streak."; +Close = "Close"; /* Onboarding */ OnboardingTitle1 = "Choose"; @@ -502,6 +503,39 @@ CodeEditorCurrentTheme = "Current: %@"; CodeEditorCurrentFontSize = "Current: %@pt"; CodeEditorSettingsTooltip = "You can customize code editor"; +/* Achievements */ +Achievements = "Achievements"; +AchievementsStepsSolvedKindTitle = "Quantity matters"; +AchievementsStepsSolvedChoiceKindTitle = "I choose"; +AchievementsStepsSolvedCodeKindTitle = "Computer master"; +AchievementsStepsSolvedNumberKindTitle = "One more achievement"; +AchievementsCodeQuizzesSolvedPythonKindTitle = "Snake tamer"; +AchievementsCodeQuizzesSolvedJavaKindTitle = "Some coffee?"; +AchievementsCodeQuizzesSolvedCppKindTitle = "Developer.cpp"; +AchievementsCertificatesRegularCountKindTitle = "Graduate"; +AchievementsCertificatesDistinctionCountKindTitle = "Distinct student"; +AchievementsCourseReviewsCountKindTitle = "Critic"; +AchievementsStepsSolvedStreakKindTitle = "Sapper"; +AchievementsActiveDaysStreakKindTitle = "Regular user"; +AchievementsStepsSolvedKindDescription = "Solve %@ tasks correctly"; +AchievementsStepsSolvedChoiceKindDescription = "Solve %@ choice tasks correctly"; +AchievementsStepsSolvedCodeKindDescription = "Solve %@ code tasks correctly"; +AchievementsStepsSolvedNumberKindDescription = "Solve %@ number tasks correctly"; +AchievementsCodeQuizzesSolvedPythonKindDescription = "Use Python to solve %@ problems correctly"; +AchievementsCodeQuizzesSolvedJavaKindDescription = "Use Java to solve %@ problems correctly"; +AchievementsCodeQuizzesSolvedCppKindDescription = "Use C++ to solve %@ problems correctly"; +AchievementsCertificatesRegularCountKindDescription = "Earn %@ certificates"; +AchievementsCertificatesDistinctionCountKindDescription = "Earn %@ certificates with distinction"; +AchievementsCourseReviewsCountKindDescription = "Write %@ course reviews"; +AchievementsStepsSolvedStreakKindDescription = "Correctly solve %@ tasks in a row"; +AchievementsActiveDaysStreakKindDescription = "Solve at least one task at %@ days in a row"; +AchievementsNextLevel = "%@ for the next level"; +AchievementsLevel = "Level %@ / %@"; +AchievementsLevelNotObtained = "Not obtained yet"; +AchievementsUnknownKindTitle = "Unknown achievement"; +AchievementsUnknownKindDescription = "Complete more challenges to unlock"; +AchievementsShareText = "I've unlocked achievement «%@» at Stepik.org. Join and beat my record!"; + /* Personal deadlines */ CourseInfo = "Course Info"; EditSchedule = "Edit schedule"; diff --git a/Stepic/ru.lproj/Localizable.strings b/Stepic/ru.lproj/Localizable.strings index e376000516..217663484b 100644 --- a/Stepic/ru.lproj/Localizable.strings +++ b/Stepic/ru.lproj/Localizable.strings @@ -339,6 +339,7 @@ SolveStreaksDaysCount = "Вы решаете %@ %@ подряд. Отлично! SolveSomethingToday = "Решите что-нибудь сегодня, чтобы улучшить свой рекорд."; StreakAlertMessageNoStreak = "Решайте задачи каждый день и улучшайте личный рекорд! Хотели бы Вы получать уведомления, чтобы не забывать учиться каждый день? Эту опцию можно изменить в настройках."; +Close = "Закрыть"; /* Onboarding */ OnboardingTitle1 = "Выбирайте"; @@ -503,6 +504,39 @@ CodeEditorCurrentTheme = "Используется: %@"; CodeEditorCurrentFontSize = "Используется: %@pt"; CodeEditorSettingsTooltip = "Вы можете настроить редактор кода"; +/* Achievements */ +Achievements = "Достижения"; +AchievementsStepsSolvedKindTitle = "Главное – количество"; +AchievementsStepsSolvedChoiceKindTitle = "Я выбираю"; +AchievementsStepsSolvedCodeKindTitle = "Компьютерный мастер"; +AchievementsStepsSolvedNumberKindTitle = "Ещё одно достижение"; +AchievementsCodeQuizzesSolvedPythonKindTitle = "Укротитель змей"; +AchievementsCodeQuizzesSolvedJavaKindTitle = "Немного кофе?"; +AchievementsCodeQuizzesSolvedCppKindTitle = "Developer.cpp"; +AchievementsCertificatesRegularCountKindTitle = "Выпускник"; +AchievementsCertificatesDistinctionCountKindTitle = "Отличник"; +AchievementsCourseReviewsCountKindTitle = "Критик"; +AchievementsStepsSolvedStreakKindTitle = "Сапёр"; +AchievementsActiveDaysStreakKindTitle = "Постоянный пользователь"; +AchievementsStepsSolvedKindDescription = "Решите правильно %@ заданий"; +AchievementsStepsSolvedChoiceKindDescription = "Решите правильно %@ заданий с выбором ответа"; +AchievementsStepsSolvedCodeKindDescription = "Решите правильно %@ заданий на программирование"; +AchievementsStepsSolvedNumberKindDescription = "Решите правильно %@ заданий с числами"; +AchievementsCodeQuizzesSolvedPythonKindDescription = "Решите правильно %@ задач на Python"; +AchievementsCodeQuizzesSolvedJavaKindDescription = "Решите правильно %@ задач на Java"; +AchievementsCodeQuizzesSolvedCppKindDescription = "Решите правильно %@ задач на C++"; +AchievementsCertificatesRegularCountKindDescription = "Получите %@ сертификатов"; +AchievementsCertificatesDistinctionCountKindDescription = "Получите %@ сертификатов с отличием"; +AchievementsCourseReviewsCountKindDescription = "Оставьте %@ отзывов о курсах"; +AchievementsStepsSolvedStreakKindDescription = "Решите правильно %@ заданий подряд"; +AchievementsActiveDaysStreakKindDescription = "Решайте как минимум одну задачу на протяжении %@ дней"; +AchievementsNextLevel = "%@ до следующего уровня"; +AchievementsLevel = "Уровень %@ / %@"; +AchievementsLevelNotObtained = "Ещё не получено"; +AchievementsUnknownKindTitle = "Неизвестное достижение"; +AchievementsUnknownKindDescription = "Решайте больше заданий, чтобы открыть"; +AchievementsShareText = "Я получил достижение «%@» на Stepik.org. Попробуй обогнать!"; + /* Personal deadlines */ CourseInfo = "Инфо о курсе"; EditSchedule = "Редактировать расписание";