diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index 0d5f338ef1..988a0d55ba 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -1849,6 +1849,60 @@ 2C315E6F1F0A947D0039E4F0 /* LessonPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0899842B1ECDE194005C0B27 /* LessonPresenter.swift */; }; 2C315E701F0A947D0039E4F0 /* LessonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0899842E1ECDE19E005C0B27 /* LessonView.swift */; }; 2C315E711F0A947D0039E4F0 /* PagerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C1943F1ED0A05D00A41B72 /* PagerController.swift */; }; + 2C35C4A41F4DA3E8002F3BF4 /* RatingsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C49D1F4DA3CA002F3BF4 /* RatingsAPI.swift */; }; + 2C35C4A51F4DA3E9002F3BF4 /* RatingsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C49D1F4DA3CA002F3BF4 /* RatingsAPI.swift */; }; + 2C35C4A61F4DA3E9002F3BF4 /* RatingsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C49D1F4DA3CA002F3BF4 /* RatingsAPI.swift */; }; + 2C35C4A71F4DA3EA002F3BF4 /* RatingsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C49D1F4DA3CA002F3BF4 /* RatingsAPI.swift */; }; + 2C35C4A81F4DA3EA002F3BF4 /* RatingsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C49D1F4DA3CA002F3BF4 /* RatingsAPI.swift */; }; + 2C35C4A91F4DA3EA002F3BF4 /* RatingsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C49D1F4DA3CA002F3BF4 /* RatingsAPI.swift */; }; + 2C35C4AA1F4DA3EF002F3BF4 /* LeaderboardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4A01F4DA3E1002F3BF4 /* LeaderboardTableViewCell.swift */; }; + 2C35C4AB1F4DA3F0002F3BF4 /* LeaderboardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4A01F4DA3E1002F3BF4 /* LeaderboardTableViewCell.swift */; }; + 2C35C4AC1F4DA3F1002F3BF4 /* LeaderboardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4A01F4DA3E1002F3BF4 /* LeaderboardTableViewCell.swift */; }; + 2C35C4AD1F4DA3F1002F3BF4 /* LeaderboardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4A01F4DA3E1002F3BF4 /* LeaderboardTableViewCell.swift */; }; + 2C35C4AE1F4DA3F1002F3BF4 /* LeaderboardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4A01F4DA3E1002F3BF4 /* LeaderboardTableViewCell.swift */; }; + 2C35C4AF1F4DA3F2002F3BF4 /* LeaderboardTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4A01F4DA3E1002F3BF4 /* LeaderboardTableViewCell.swift */; }; + 2C35C4B01F4DA3FE002F3BF4 /* LeaderboardTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4A11F4DA3E1002F3BF4 /* LeaderboardTableViewCell.xib */; }; + 2C35C4B11F4DA3FF002F3BF4 /* LeaderboardTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4A11F4DA3E1002F3BF4 /* LeaderboardTableViewCell.xib */; }; + 2C35C4B21F4DA3FF002F3BF4 /* LeaderboardTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4A11F4DA3E1002F3BF4 /* LeaderboardTableViewCell.xib */; }; + 2C35C4B31F4DA400002F3BF4 /* LeaderboardTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4A11F4DA3E1002F3BF4 /* LeaderboardTableViewCell.xib */; }; + 2C35C4B41F4DA400002F3BF4 /* LeaderboardTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4A11F4DA3E1002F3BF4 /* LeaderboardTableViewCell.xib */; }; + 2C35C4B51F4DA401002F3BF4 /* LeaderboardTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4A11F4DA3E1002F3BF4 /* LeaderboardTableViewCell.xib */; }; + 2C35C4B61F4DA411002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */; }; + 2C35C4B71F4DA413002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */; }; + 2C35C4B81F4DA415002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */; }; + 2C35C4B91F4DA416002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */; }; + 2C35C4BA1F4DA416002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */; }; + 2C35C4BB1F4DA417002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */; }; + 2C35C4BC1F4DA41A002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4991F4DA3B6002F3BF4 /* AdaptiveRatingsPresenter.swift */; }; + 2C35C4BD1F4DA41B002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4991F4DA3B6002F3BF4 /* AdaptiveRatingsPresenter.swift */; }; + 2C35C4BE1F4DA41B002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4991F4DA3B6002F3BF4 /* AdaptiveRatingsPresenter.swift */; }; + 2C35C4BF1F4DA41C002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4991F4DA3B6002F3BF4 /* AdaptiveRatingsPresenter.swift */; }; + 2C35C4C01F4DA41C002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4991F4DA3B6002F3BF4 /* AdaptiveRatingsPresenter.swift */; }; + 2C35C4C11F4DA41D002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C35C4991F4DA3B6002F3BF4 /* AdaptiveRatingsPresenter.swift */; }; + 2C35C4D51F4DA477002F3BF4 /* adjectives_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C51F4DA462002F3BF4 /* adjectives_f.plist */; }; + 2C35C4D61F4DA478002F3BF4 /* adjectives_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C51F4DA462002F3BF4 /* adjectives_f.plist */; }; + 2C35C4D71F4DA478002F3BF4 /* adjectives_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C51F4DA462002F3BF4 /* adjectives_f.plist */; }; + 2C35C4D81F4DA479002F3BF4 /* adjectives_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C51F4DA462002F3BF4 /* adjectives_f.plist */; }; + 2C35C4D91F4DA479002F3BF4 /* adjectives_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C51F4DA462002F3BF4 /* adjectives_f.plist */; }; + 2C35C4DA1F4DA47A002F3BF4 /* adjectives_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C51F4DA462002F3BF4 /* adjectives_f.plist */; }; + 2C35C4DB1F4DA47E002F3BF4 /* adjectives_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C71F4DA462002F3BF4 /* adjectives_m.plist */; }; + 2C35C4DC1F4DA47F002F3BF4 /* adjectives_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C71F4DA462002F3BF4 /* adjectives_m.plist */; }; + 2C35C4DD1F4DA47F002F3BF4 /* adjectives_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C71F4DA462002F3BF4 /* adjectives_m.plist */; }; + 2C35C4DE1F4DA47F002F3BF4 /* adjectives_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C71F4DA462002F3BF4 /* adjectives_m.plist */; }; + 2C35C4DF1F4DA480002F3BF4 /* adjectives_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C71F4DA462002F3BF4 /* adjectives_m.plist */; }; + 2C35C4E01F4DA481002F3BF4 /* adjectives_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C71F4DA462002F3BF4 /* adjectives_m.plist */; }; + 2C35C4E11F4DA486002F3BF4 /* nouns_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C91F4DA462002F3BF4 /* nouns_f.plist */; }; + 2C35C4E21F4DA486002F3BF4 /* nouns_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C91F4DA462002F3BF4 /* nouns_f.plist */; }; + 2C35C4E31F4DA487002F3BF4 /* nouns_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C91F4DA462002F3BF4 /* nouns_f.plist */; }; + 2C35C4E41F4DA487002F3BF4 /* nouns_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C91F4DA462002F3BF4 /* nouns_f.plist */; }; + 2C35C4E51F4DA488002F3BF4 /* nouns_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C91F4DA462002F3BF4 /* nouns_f.plist */; }; + 2C35C4E61F4DA488002F3BF4 /* nouns_f.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4C91F4DA462002F3BF4 /* nouns_f.plist */; }; + 2C35C4E71F4DA48D002F3BF4 /* nouns_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4CB1F4DA462002F3BF4 /* nouns_m.plist */; }; + 2C35C4E81F4DA48E002F3BF4 /* nouns_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4CB1F4DA462002F3BF4 /* nouns_m.plist */; }; + 2C35C4E91F4DA48E002F3BF4 /* nouns_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4CB1F4DA462002F3BF4 /* nouns_m.plist */; }; + 2C35C4EA1F4DA48F002F3BF4 /* nouns_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4CB1F4DA462002F3BF4 /* nouns_m.plist */; }; + 2C35C4EB1F4DA48F002F3BF4 /* nouns_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4CB1F4DA462002F3BF4 /* nouns_m.plist */; }; + 2C35C4EC1F4DA48F002F3BF4 /* nouns_m.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2C35C4CB1F4DA462002F3BF4 /* nouns_m.plist */; }; 2C3DA0041F3C68B700D74968 /* AchievementNotificationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2C3DA0031F3C68B700D74968 /* AchievementNotificationView.xib */; }; 2C4DA9E91F38729500E392FA /* LocalNotificationsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4DA9E81F38729500E392FA /* LocalNotificationsHelper.swift */; }; 2C5F48D71F0F6FBD00C3A398 /* AdaptiveStepPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C5F48D51F0F6ED800C3A398 /* AdaptiveStepPresenter.swift */; }; @@ -4296,6 +4350,19 @@ 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 = ""; }; 2C2544081F3480BE004DB3D9 /* AchievementNotificationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AchievementNotificationView.swift; sourceTree = ""; }; + 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveAchievementsPresenter.swift; sourceTree = ""; }; + 2C35C4991F4DA3B6002F3BF4 /* AdaptiveRatingsPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveRatingsPresenter.swift; sourceTree = ""; }; + 2C35C49D1F4DA3CA002F3BF4 /* RatingsAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RatingsAPI.swift; sourceTree = ""; }; + 2C35C4A01F4DA3E1002F3BF4 /* LeaderboardTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeaderboardTableViewCell.swift; sourceTree = ""; }; + 2C35C4A11F4DA3E1002F3BF4 /* LeaderboardTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LeaderboardTableViewCell.xib; sourceTree = ""; }; + 2C35C4C61F4DA462002F3BF4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ru; path = LeaderboardNames/ru.lproj/adjectives_f.plist; sourceTree = ""; }; + 2C35C4C81F4DA462002F3BF4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ru; path = LeaderboardNames/ru.lproj/adjectives_m.plist; sourceTree = ""; }; + 2C35C4CA1F4DA462002F3BF4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ru; path = LeaderboardNames/ru.lproj/nouns_f.plist; sourceTree = ""; }; + 2C35C4CC1F4DA462002F3BF4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = ru; path = LeaderboardNames/ru.lproj/nouns_m.plist; sourceTree = ""; }; + 2C35C4D11F4DA467002F3BF4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = LeaderboardNames/en.lproj/adjectives_f.plist; sourceTree = ""; }; + 2C35C4D21F4DA46A002F3BF4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = LeaderboardNames/en.lproj/adjectives_m.plist; sourceTree = ""; }; + 2C35C4D31F4DA46E002F3BF4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = LeaderboardNames/en.lproj/nouns_f.plist; sourceTree = ""; }; + 2C35C4D41F4DA471002F3BF4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = LeaderboardNames/en.lproj/nouns_m.plist; sourceTree = ""; }; 2C3DA0031F3C68B700D74968 /* AchievementNotificationView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AchievementNotificationView.xib; sourceTree = ""; }; 2C4DA9E81F38729500E392FA /* LocalNotificationsHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalNotificationsHelper.swift; sourceTree = ""; }; 2C5F48D51F0F6ED800C3A398 /* AdaptiveStepPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveStepPresenter.swift; sourceTree = ""; }; @@ -6152,6 +6219,34 @@ name = AchievementNotificationView; sourceTree = ""; }; + 2C35C49C1F4DA3BD002F3BF4 /* API */ = { + isa = PBXGroup; + children = ( + 2C35C49D1F4DA3CA002F3BF4 /* RatingsAPI.swift */, + ); + name = API; + sourceTree = ""; + }; + 2C35C49F1F4DA3D2002F3BF4 /* LeaderboardTableViewCell */ = { + isa = PBXGroup; + children = ( + 2C35C4A01F4DA3E1002F3BF4 /* LeaderboardTableViewCell.swift */, + 2C35C4A11F4DA3E1002F3BF4 /* LeaderboardTableViewCell.xib */, + ); + name = LeaderboardTableViewCell; + sourceTree = ""; + }; + 2C35C4C21F4DA43D002F3BF4 /* Leaderboard names */ = { + isa = PBXGroup; + children = ( + 2C35C4C51F4DA462002F3BF4 /* adjectives_f.plist */, + 2C35C4C71F4DA462002F3BF4 /* adjectives_m.plist */, + 2C35C4C91F4DA462002F3BF4 /* nouns_f.plist */, + 2C35C4CB1F4DA462002F3BF4 /* nouns_m.plist */, + ); + name = "Leaderboard names"; + sourceTree = ""; + }; 2C5F48D41F0F6EB700C3A398 /* AdaptiveStep */ = { isa = PBXGroup; children = ( @@ -6165,6 +6260,8 @@ 2C5F48D81F0FA01C00C3A398 /* AdaptiveSteps */ = { isa = PBXGroup; children = ( + 2C35C4981F4DA3B6002F3BF4 /* AdaptiveAchievementsPresenter.swift */, + 2C35C4991F4DA3B6002F3BF4 /* AdaptiveRatingsPresenter.swift */, 86ABC66F1E9687A10012E8A6 /* AdaptiveStepsViewController.swift */, 2C5F48D91F0FA04300C3A398 /* AdaptiveStepsPresenter.swift */, ); @@ -6365,6 +6462,7 @@ 2CFB1C521F14C7EA00CD0A4C /* Supporting Files */ = { isa = PBXGroup; children = ( + 2C35C4C21F4DA43D002F3BF4 /* Leaderboard names */, 2CFB1C531F14C81100CD0A4C /* Onboarding content */, ); name = "Supporting Files"; @@ -6426,6 +6524,7 @@ 86ABC6651E9685E70012E8A6 /* Views */ = { isa = PBXGroup; children = ( + 2C35C49F1F4DA3D2002F3BF4 /* LeaderboardTableViewCell */, 2C2544071F348085004DB3D9 /* AchievementNotificationView */, 2CB425751F2B3BBE00D15D93 /* ProgressTableViewCell */, 866DDDC41EA8060400BAD48E /* CardOverlayView */, @@ -6460,6 +6559,7 @@ 86EF29351E83CEA900F8D214 /* Adaptive Course */ = { isa = PBXGroup; children = ( + 2C35C49C1F4DA3BD002F3BF4 /* API */, 2C8652561F4B2C2300D51654 /* Content */, 2CEDA35E1F336F0E005F4A5D /* Managers */, 2C6ABA611F1E009E005A3BCF /* Alerts */, @@ -7088,6 +7188,7 @@ 2C1B62501F4C4AEF00236804 /* step3.html in Resources */, 2C1B62511F4C4AEF00236804 /* jquery-3.2.1.min.js in Resources */, 2C1B62521F4C4AEF00236804 /* wysiwyg.css in Resources */, + 2C35C4D91F4DA479002F3BF4 /* adjectives_f.plist in Resources */, 2C1B62531F4C4AEF00236804 /* Localizable.strings in Resources */, 2C1B62541F4C4AEF00236804 /* arrow_left.svg in Resources */, 2C1B62561F4C4AEF00236804 /* LoadingPaginationView.xib in Resources */, @@ -7096,6 +7197,7 @@ 2C1B625A1F4C4AEF00236804 /* StreaksView.xib in Resources */, 2C1B625B1F4C4AEF00236804 /* overlay_hard.png in Resources */, 2C1B625C1F4C4AEF00236804 /* RateAppViewController.xib in Resources */, + 2C35C4B41F4DA400002F3BF4 /* LeaderboardTableViewCell.xib in Resources */, 2C1B625D1F4C4AEF00236804 /* overlay_simple.png in Resources */, 2C1B625E1F4C4AEF00236804 /* CurrentBestStreakViewController.xib in Resources */, 2C1B625F1F4C4AEF00236804 /* Main.storyboard in Resources */, @@ -7104,6 +7206,7 @@ 2C1B62621F4C4AEF00236804 /* PlaceholderView.storyboard in Resources */, 2C1B62631F4C4AEF00236804 /* VideoDownloadView.xib in Resources */, 2C1B62651F4C4AEF00236804 /* WarningView.xib in Resources */, + 2C35C4EB1F4DA48F002F3BF4 /* nouns_m.plist in Resources */, 2C1B62671F4C4AEF00236804 /* StepTabView.xib in Resources */, 2C1B62681F4C4AEF00236804 /* DiscussionCountView.xib in Resources */, 2C1B62691F4C4AEF00236804 /* CongratulationViewController.xib in Resources */, @@ -7114,6 +7217,7 @@ 2C1B626E1F4C4AEF00236804 /* StreakAlertViewController.xib in Resources */, 2C1B62CB1F4C517900236804 /* Tokens.plist in Resources */, 2C1B62701F4C4AEF00236804 /* StepicVideoPlayerViewController.xib in Resources */, + 2C35C4DE1F4DA47F002F3BF4 /* adjectives_m.plist in Resources */, 2C1B62721F4C4AEF00236804 /* step2.html in Resources */, 2C1B62731F4C4AEF00236804 /* CodeInputAccessoryView.xib in Resources */, 2C1B62741F4C4AEF00236804 /* SignInCoursesTableViewCell.xib in Resources */, @@ -7149,6 +7253,7 @@ 2C1B62991F4C4AEF00236804 /* SectionTableViewCell.xib in Resources */, 2C1B629A1F4C4AEF00236804 /* UnitTableViewCell.xib in Resources */, 2C1B629B1F4C4AEF00236804 /* Images.xcassets in Resources */, + 2C35C4E61F4DA488002F3BF4 /* nouns_f.plist in Resources */, 2C1B62C71F4C4B6C00236804 /* icon.png in Resources */, 2C1B629C1F4C4AEF00236804 /* ProgressTableViewCell.xib in Resources */, 2C1B629D1F4C4AEF00236804 /* Auth.storyboard in Resources */, @@ -7173,6 +7278,7 @@ 2C1B64611F4C590700236804 /* step3.html in Resources */, 2C1B64621F4C590700236804 /* jquery-3.2.1.min.js in Resources */, 2C1B64631F4C590700236804 /* wysiwyg.css in Resources */, + 2C35C4DA1F4DA47A002F3BF4 /* adjectives_f.plist in Resources */, 2C1B64641F4C590700236804 /* Localizable.strings in Resources */, 2C1B64651F4C590700236804 /* arrow_left.svg in Resources */, 2C1B64671F4C590700236804 /* LoadingPaginationView.xib in Resources */, @@ -7181,6 +7287,7 @@ 2C1B646B1F4C590700236804 /* StreaksView.xib in Resources */, 2C1B646C1F4C590700236804 /* overlay_hard.png in Resources */, 2C1B646D1F4C590700236804 /* RateAppViewController.xib in Resources */, + 2C35C4B51F4DA401002F3BF4 /* LeaderboardTableViewCell.xib in Resources */, 2C1B646E1F4C590700236804 /* overlay_simple.png in Resources */, 2C1B646F1F4C590700236804 /* CurrentBestStreakViewController.xib in Resources */, 2C1B64701F4C590700236804 /* Main.storyboard in Resources */, @@ -7189,6 +7296,7 @@ 2C1B64731F4C590700236804 /* PlaceholderView.storyboard in Resources */, 2C1B64741F4C590700236804 /* VideoDownloadView.xib in Resources */, 2C1B64761F4C590700236804 /* WarningView.xib in Resources */, + 2C35C4EC1F4DA48F002F3BF4 /* nouns_m.plist in Resources */, 2C1B64781F4C590700236804 /* StepTabView.xib in Resources */, 2C1B64791F4C590700236804 /* DiscussionCountView.xib in Resources */, 2C1B647A1F4C590700236804 /* CongratulationViewController.xib in Resources */, @@ -7199,6 +7307,7 @@ 2C1B64801F4C590700236804 /* StreakAlertViewController.xib in Resources */, 2C1B64E01F4C596500236804 /* Config.plist in Resources */, 2C1B64DF1F4C596500236804 /* Tokens.plist in Resources */, + 2C35C4DF1F4DA480002F3BF4 /* adjectives_m.plist in Resources */, 2C1B64831F4C590700236804 /* StepicVideoPlayerViewController.xib in Resources */, 2C1B64851F4C590700236804 /* step2.html in Resources */, 2C1B64861F4C590700236804 /* CodeInputAccessoryView.xib in Resources */, @@ -7234,6 +7343,7 @@ 2C1B64AC1F4C590700236804 /* RefreshTableViewCell.xib in Resources */, 2C1B64AE1F4C590700236804 /* CourseTableViewCell.xib in Resources */, 2C1B64AF1F4C590700236804 /* SectionTableViewCell.xib in Resources */, + 2C35C4E51F4DA488002F3BF4 /* nouns_f.plist in Resources */, 2C1B64B01F4C590700236804 /* UnitTableViewCell.xib in Resources */, 2C1B64B11F4C590700236804 /* Images.xcassets in Resources */, 2C1B64B31F4C590700236804 /* ProgressTableViewCell.xib in Resources */, @@ -7259,9 +7369,11 @@ 2C8653FF1F4B33DE00D51654 /* jquery-3.2.1.min.js in Resources */, 2C8654001F4B33DE00D51654 /* wysiwyg.css in Resources */, 2C8654021F4B33DE00D51654 /* Localizable.strings in Resources */, + 2C35C4D61F4DA478002F3BF4 /* adjectives_f.plist in Resources */, 2C8654031F4B33DE00D51654 /* arrow_left.svg in Resources */, 2C8654041F4B33DE00D51654 /* LoadingPaginationView.xib in Resources */, 2C8654051F4B33DE00D51654 /* CertificateTableViewCell.xib in Resources */, + 2C35C4DC1F4DA47F002F3BF4 /* adjectives_m.plist in Resources */, 2C8654061F4B33DE00D51654 /* CertificatesStoryboard.storyboard in Resources */, 2C8654071F4B33DE00D51654 /* StreaksView.xib in Resources */, 2C8654081F4B33DE00D51654 /* overlay_hard.png in Resources */, @@ -7272,6 +7384,7 @@ 2C86540F1F4B33DE00D51654 /* StepCardView.xib in Resources */, 2C8654101F4B33DE00D51654 /* CodeSuggestionsTableViewController.xib in Resources */, 2C8654111F4B33DE00D51654 /* PlaceholderView.storyboard in Resources */, + 2C35C4E21F4DA486002F3BF4 /* nouns_f.plist in Resources */, 2C8654121F4B33DE00D51654 /* VideoDownloadView.xib in Resources */, 2C86548B1F4B3A6200D51654 /* Icons.xcassets in Resources */, 2C8654131F4B33DE00D51654 /* WarningView.xib in Resources */, @@ -7294,6 +7407,7 @@ 2CF44FF31F4B4F3E00C19012 /* icon.png in Resources */, 2C8654221F4B33DE00D51654 /* DownloadTableViewCell.xib in Resources */, 2C86548A1F4B3A5F00D51654 /* Tokens.plist in Resources */, + 2C35C4E81F4DA48E002F3BF4 /* nouns_m.plist in Resources */, 2C8654231F4B33DE00D51654 /* DiscussionWebTableViewCell.xib in Resources */, 2C8654241F4B33DE00D51654 /* CardOverlayView.xib in Resources */, 2C8654251F4B33DE00D51654 /* DiscussionTableViewCell.xib in Resources */, @@ -7308,6 +7422,7 @@ 2C8654301F4B33DE00D51654 /* CodeInputAccessoryCollectionViewCell.xib in Resources */, 2C8654311F4B33DE00D51654 /* ChoiceQuizTableViewCell.xib in Resources */, 2C8654321F4B33DE00D51654 /* SortingQuizTableViewCell.xib in Resources */, + 2C35C4B01F4DA3FE002F3BF4 /* LeaderboardTableViewCell.xib in Resources */, 2C8654331F4B33DE00D51654 /* CodeQuizToolbarView.xib in Resources */, 2C8654341F4B33DE00D51654 /* FillBlanksTextTableViewCell.xib in Resources */, 2C8654351F4B33DE00D51654 /* FillBlanksInputTableViewCell.xib in Resources */, @@ -7345,9 +7460,11 @@ 2C89AAE31F4C289900227C3B /* step3.html in Resources */, 2C89AAE41F4C289900227C3B /* jquery-3.2.1.min.js in Resources */, 2C89AAE51F4C289900227C3B /* wysiwyg.css in Resources */, + 2C35C4D71F4DA478002F3BF4 /* adjectives_f.plist in Resources */, 2C89AAE61F4C289900227C3B /* Localizable.strings in Resources */, 2C89AAE71F4C289900227C3B /* arrow_left.svg in Resources */, 2C89AAE81F4C289900227C3B /* LoadingPaginationView.xib in Resources */, + 2C35C4DD1F4DA47F002F3BF4 /* adjectives_m.plist in Resources */, 2C89AAE91F4C289900227C3B /* CertificateTableViewCell.xib in Resources */, 2C89AAEA1F4C289900227C3B /* CertificatesStoryboard.storyboard in Resources */, 2C89AB441F4C290800227C3B /* Tokens.plist in Resources */, @@ -7358,6 +7475,7 @@ 2C89AAEF1F4C289900227C3B /* CurrentBestStreakViewController.xib in Resources */, 2C89AAF01F4C289900227C3B /* Main.storyboard in Resources */, 2C89AAF11F4C289900227C3B /* StepCardView.xib in Resources */, + 2C35C4E31F4DA487002F3BF4 /* nouns_f.plist in Resources */, 2C89AAF21F4C289900227C3B /* CodeSuggestionsTableViewController.xib in Resources */, 2C89AAF31F4C289900227C3B /* PlaceholderView.storyboard in Resources */, 2C89AAF41F4C289900227C3B /* VideoDownloadView.xib in Resources */, @@ -7380,6 +7498,7 @@ 2C89AB081F4C289900227C3B /* DownloadTableViewCell.xib in Resources */, 2C89AB3E1F4C28F400227C3B /* ru.lproj in Resources */, 2C89AB0A1F4C289900227C3B /* DiscussionWebTableViewCell.xib in Resources */, + 2C35C4E91F4DA48E002F3BF4 /* nouns_m.plist in Resources */, 2C89AB0B1F4C289900227C3B /* CardOverlayView.xib in Resources */, 2C89AB0D1F4C289900227C3B /* DiscussionTableViewCell.xib in Resources */, 2C89AB0E1F4C289900227C3B /* DiscussionsViewController.xib in Resources */, @@ -7394,6 +7513,7 @@ 2C89AB161F4C289900227C3B /* CodeInputAccessoryCollectionViewCell.xib in Resources */, 2C89AB3C1F4C28EE00227C3B /* en.lproj in Resources */, 2C89AB171F4C289900227C3B /* ChoiceQuizTableViewCell.xib in Resources */, + 2C35C4B11F4DA3FF002F3BF4 /* LeaderboardTableViewCell.xib in Resources */, 2C89AB181F4C289900227C3B /* SortingQuizTableViewCell.xib in Resources */, 2C89AB191F4C289900227C3B /* CodeQuizToolbarView.xib in Resources */, 2C89AB1A1F4C289900227C3B /* FillBlanksTextTableViewCell.xib in Resources */, @@ -7430,6 +7550,7 @@ 2C9733061F4C393C00AC9301 /* icon.png in Resources */, 2C9732971F4C38F600AC9301 /* step3.html in Resources */, 2C9732981F4C38F600AC9301 /* jquery-3.2.1.min.js in Resources */, + 2C35C4D81F4DA479002F3BF4 /* adjectives_f.plist in Resources */, 2C9732991F4C38F600AC9301 /* wysiwyg.css in Resources */, 2C97329A1F4C38F600AC9301 /* Localizable.strings in Resources */, 2C97329B1F4C38F600AC9301 /* arrow_left.svg in Resources */, @@ -7438,6 +7559,7 @@ 2C97329D1F4C38F600AC9301 /* CertificateTableViewCell.xib in Resources */, 2C97330D1F4C394A00AC9301 /* Tokens.plist in Resources */, 2C97329E1F4C38F600AC9301 /* CertificatesStoryboard.storyboard in Resources */, + 2C35C4B31F4DA400002F3BF4 /* LeaderboardTableViewCell.xib in Resources */, 2C97329F1F4C38F600AC9301 /* StreaksView.xib in Resources */, 2C9732A01F4C38F600AC9301 /* overlay_hard.png in Resources */, 2C9732A11F4C38F600AC9301 /* RateAppViewController.xib in Resources */, @@ -7446,6 +7568,7 @@ 2C9732A41F4C38F600AC9301 /* Main.storyboard in Resources */, 2C9732A51F4C38F600AC9301 /* StepCardView.xib in Resources */, 2C9732A61F4C38F600AC9301 /* CodeSuggestionsTableViewController.xib in Resources */, + 2C35C4EA1F4DA48F002F3BF4 /* nouns_m.plist in Resources */, 2C9732A71F4C38F600AC9301 /* PlaceholderView.storyboard in Resources */, 2C9732A81F4C38F600AC9301 /* VideoDownloadView.xib in Resources */, 2C9732AA1F4C38F600AC9301 /* WarningView.xib in Resources */, @@ -7456,6 +7579,7 @@ 2C9732B01F4C38F600AC9301 /* StepikAlertViewController.xib in Resources */, 2C9732B11F4C38F600AC9301 /* FullscreenCodeQuizViewController.xib in Resources */, 2C9732B21F4C38F600AC9301 /* AchievementTableViewCell.xib in Resources */, + 2C35C4E01F4DA481002F3BF4 /* adjectives_m.plist in Resources */, 2C9732B31F4C38F600AC9301 /* StreakAlertViewController.xib in Resources */, 2C9732B51F4C38F600AC9301 /* StepicVideoPlayerViewController.xib in Resources */, 2C9732B71F4C38F600AC9301 /* step2.html in Resources */, @@ -7491,6 +7615,7 @@ 2C9732D61F4C38F600AC9301 /* UserPreferences.storyboard in Resources */, 2C9732D71F4C38F600AC9301 /* RefreshTableViewCell.xib in Resources */, 2C9732D91F4C38F600AC9301 /* CourseTableViewCell.xib in Resources */, + 2C35C4E41F4DA487002F3BF4 /* nouns_f.plist in Resources */, 2C9732DA1F4C38F600AC9301 /* SectionTableViewCell.xib in Resources */, 2C9732DB1F4C38F600AC9301 /* UnitTableViewCell.xib in Resources */, 2C9732DC1F4C38F600AC9301 /* Images.xcassets in Resources */, @@ -7552,6 +7677,7 @@ 866DDDC61EA8076D00BAD48E /* CardOverlayView.xib in Resources */, 86AE947A1E84511A00F67691 /* DiscussionTableViewCell.xib in Resources */, 86AE947B1E84511A00F67691 /* DiscussionsViewController.xib in Resources */, + 2C35C4D51F4DA477002F3BF4 /* adjectives_f.plist in Resources */, 2C8652681F4B2FA500D51654 /* step1.html in Resources */, 2C8652641F4B2F7D00D51654 /* GoogleService-Info.plist in Resources */, 86AE947C1E84511A00F67691 /* LoadMoreTableViewCell.xib in Resources */, @@ -7569,6 +7695,7 @@ 8618E3E61EA0065100FB3D0F /* StepReversedCardView.xib in Resources */, 86AE94851E84511A00F67691 /* FillBlanksChoiceTableViewCell.xib in Resources */, 2C8652621F4B2F7D00D51654 /* Auth.plist in Resources */, + 2C35C4DB1F4DA47E002F3BF4 /* adjectives_m.plist in Resources */, 86AE94861E84511A00F67691 /* TeacherCollectionViewCell.xib in Resources */, 86AE94871E84511A00F67691 /* TitleTextTableViewCell.xib in Resources */, 2C9704211F3B0B7F00C36F0A /* step4.html in Resources */, @@ -7579,14 +7706,17 @@ 86AE948C1E84511A00F67691 /* UnitTableViewCell.xib in Resources */, 86AE948D1E84511A00F67691 /* Images.xcassets in Resources */, 2C6640DB1F30BCCB0033A274 /* ProgressTableViewCell.xib in Resources */, + 2C35C4B21F4DA3FF002F3BF4 /* LeaderboardTableViewCell.xib in Resources */, 86AE946C1E844EEF00F67691 /* Auth.storyboard in Resources */, 86AE946D1E844EEF00F67691 /* SocialNetworkCollectionViewCell.xib in Resources */, + 2C35C4E71F4DA48D002F3BF4 /* nouns_m.plist in Resources */, 2C8652651F4B2F7D00D51654 /* Tokens.plist in Resources */, 2C3DA0041F3C68B700D74968 /* AchievementNotificationView.xib in Resources */, 86EF29411E83CEA900F8D214 /* AdaptiveLaunchScreen.storyboard in Resources */, 085E9E721F138C4200D6A4BC /* CodeSuggestionTableViewCell.xib in Resources */, 86EF293E1E83CEA900F8D214 /* AdaptiveAssets.xcassets in Resources */, 86EF293C1E83CEA900F8D214 /* AdaptiveMain.storyboard in Resources */, + 2C35C4E11F4DA486002F3BF4 /* nouns_f.plist in Resources */, 2CFB1C681F14CCA800CD0A4C /* arrow_right.svg in Resources */, 863262FA1ECFC5E2007A20B3 /* loading_robot.gif in Resources */, ); @@ -9135,6 +9265,7 @@ 2C1B61741F4C4AEF00236804 /* StepReversedCardView.swift in Sources */, 2C1B61751F4C4AEF00236804 /* WatchSessionDataObserver.swift in Sources */, 2C1B61761F4C4AEF00236804 /* WatchSessionSender.swift in Sources */, + 2C35C4A81F4DA3EA002F3BF4 /* RatingsAPI.swift in Sources */, 2C1B61771F4C4AEF00236804 /* DataConvertable.swift in Sources */, 2C1B61781F4C4AEF00236804 /* CoursePlainEntity.swift in Sources */, 2C1B61791F4C4AEF00236804 /* CourseMetainfoEntity.swift in Sources */, @@ -9271,6 +9402,7 @@ 2C1B61FB1F4C4AEF00236804 /* User+CoreDataProperties.swift in Sources */, 2C1B61FC1F4C4AEF00236804 /* User.swift in Sources */, 2C1B61FD1F4C4AEF00236804 /* CongratulationAlertManager.swift in Sources */, + 2C35C4BB1F4DA417002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */, 2C1B61FE1F4C4AEF00236804 /* Parser.swift in Sources */, 2C1B61FF1F4C4AEF00236804 /* StepicToken.swift in Sources */, 2C1B62001F4C4AEF00236804 /* AuthInfo.swift in Sources */, @@ -9293,6 +9425,7 @@ 2C1B62111F4C4AEF00236804 /* DevicesAPI.swift in Sources */, 2C1B62121F4C4AEF00236804 /* APIDefaults.swift in Sources */, 2C1B62131F4C4AEF00236804 /* AdaptiveOnboardingPresenter.swift in Sources */, + 2C35C4AD1F4DA3F1002F3BF4 /* LeaderboardTableViewCell.swift in Sources */, 2C1B62141F4C4AEF00236804 /* DiscussionProxiesAPI.swift in Sources */, 2C1B62151F4C4AEF00236804 /* SearchQueriesView.swift in Sources */, 2C1B62161F4C4AEF00236804 /* LastStepsAPI.swift in Sources */, @@ -9324,6 +9457,7 @@ 2C1B62301F4C4AEF00236804 /* CodeSuggestionTableViewCell.swift in Sources */, 2C1B62311F4C4AEF00236804 /* PreferencesContainer.swift in Sources */, 2C1B62321F4C4AEF00236804 /* NotificationPreferencesContainer.swift in Sources */, + 2C35C4C01F4DA41C002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */, 2C1B62331F4C4AEF00236804 /* LocalNotificationManager.swift in Sources */, 2C1B62341F4C4AEF00236804 /* UIColorExtensions.swift in Sources */, 2C1B62351F4C4AEF00236804 /* StandardsExtensions.swift in Sources */, @@ -9539,6 +9673,7 @@ 2C1B63851F4C590700236804 /* StepReversedCardView.swift in Sources */, 2C1B63861F4C590700236804 /* WatchSessionDataObserver.swift in Sources */, 2C1B63871F4C590700236804 /* WatchSessionSender.swift in Sources */, + 2C35C4A91F4DA3EA002F3BF4 /* RatingsAPI.swift in Sources */, 2C1B63881F4C590700236804 /* DataConvertable.swift in Sources */, 2C1B63891F4C590700236804 /* CoursePlainEntity.swift in Sources */, 2C1B638A1F4C590700236804 /* CourseMetainfoEntity.swift in Sources */, @@ -9675,6 +9810,7 @@ 2C1B640C1F4C590700236804 /* User+CoreDataProperties.swift in Sources */, 2C1B640D1F4C590700236804 /* User.swift in Sources */, 2C1B640E1F4C590700236804 /* CongratulationAlertManager.swift in Sources */, + 2C35C4BA1F4DA416002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */, 2C1B640F1F4C590700236804 /* Parser.swift in Sources */, 2C1B64101F4C590700236804 /* StepicToken.swift in Sources */, 2C1B64111F4C590700236804 /* AuthInfo.swift in Sources */, @@ -9697,6 +9833,7 @@ 2C1B64221F4C590700236804 /* DevicesAPI.swift in Sources */, 2C1B64231F4C590700236804 /* APIDefaults.swift in Sources */, 2C1B64241F4C590700236804 /* AdaptiveOnboardingPresenter.swift in Sources */, + 2C35C4AF1F4DA3F2002F3BF4 /* LeaderboardTableViewCell.swift in Sources */, 2C1B64251F4C590700236804 /* DiscussionProxiesAPI.swift in Sources */, 2C1B64261F4C590700236804 /* SearchQueriesView.swift in Sources */, 2C1B64271F4C590700236804 /* LastStepsAPI.swift in Sources */, @@ -9728,6 +9865,7 @@ 2C1B64411F4C590700236804 /* CodeSuggestionTableViewCell.swift in Sources */, 2C1B64421F4C590700236804 /* PreferencesContainer.swift in Sources */, 2C1B64431F4C590700236804 /* NotificationPreferencesContainer.swift in Sources */, + 2C35C4C11F4DA41D002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */, 2C1B64441F4C590700236804 /* LocalNotificationManager.swift in Sources */, 2C1B64451F4C590700236804 /* UIColorExtensions.swift in Sources */, 2C1B64461F4C590700236804 /* StandardsExtensions.swift in Sources */, @@ -9943,6 +10081,7 @@ 2C8653221F4B33DE00D51654 /* StepReversedCardView.swift in Sources */, 2C8653231F4B33DE00D51654 /* WatchSessionDataObserver.swift in Sources */, 2C8653241F4B33DE00D51654 /* WatchSessionSender.swift in Sources */, + 2C35C4A51F4DA3E9002F3BF4 /* RatingsAPI.swift in Sources */, 2C8653251F4B33DE00D51654 /* DataConvertable.swift in Sources */, 2C8653261F4B33DE00D51654 /* CoursePlainEntity.swift in Sources */, 2C8653271F4B33DE00D51654 /* CourseMetainfoEntity.swift in Sources */, @@ -10079,6 +10218,7 @@ 2C8653A91F4B33DE00D51654 /* User+CoreDataProperties.swift in Sources */, 2C8653AA1F4B33DE00D51654 /* User.swift in Sources */, 2C8653AB1F4B33DE00D51654 /* CongratulationAlertManager.swift in Sources */, + 2C35C4B71F4DA413002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */, 2C8653AC1F4B33DE00D51654 /* Parser.swift in Sources */, 2C8653AD1F4B33DE00D51654 /* StepicToken.swift in Sources */, 2C8653AE1F4B33DE00D51654 /* AuthInfo.swift in Sources */, @@ -10101,6 +10241,7 @@ 2C8653BF1F4B33DE00D51654 /* DevicesAPI.swift in Sources */, 2C8653C01F4B33DE00D51654 /* APIDefaults.swift in Sources */, 2C8653C11F4B33DE00D51654 /* AdaptiveOnboardingPresenter.swift in Sources */, + 2C35C4AB1F4DA3F0002F3BF4 /* LeaderboardTableViewCell.swift in Sources */, 2C8653C21F4B33DE00D51654 /* DiscussionProxiesAPI.swift in Sources */, 2C8653C31F4B33DE00D51654 /* SearchQueriesView.swift in Sources */, 2C8653C41F4B33DE00D51654 /* LastStepsAPI.swift in Sources */, @@ -10132,6 +10273,7 @@ 2C8653DE1F4B33DE00D51654 /* CodeSuggestionTableViewCell.swift in Sources */, 2C8653DF1F4B33DE00D51654 /* PreferencesContainer.swift in Sources */, 2C8653E01F4B33DE00D51654 /* NotificationPreferencesContainer.swift in Sources */, + 2C35C4BD1F4DA41B002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */, 2C8653E11F4B33DE00D51654 /* LocalNotificationManager.swift in Sources */, 2C8653E21F4B33DE00D51654 /* UIColorExtensions.swift in Sources */, 2C8653E31F4B33DE00D51654 /* StandardsExtensions.swift in Sources */, @@ -10347,6 +10489,7 @@ 2C89AA081F4C289900227C3B /* StepReversedCardView.swift in Sources */, 2C89AA091F4C289900227C3B /* WatchSessionDataObserver.swift in Sources */, 2C89AA0A1F4C289900227C3B /* WatchSessionSender.swift in Sources */, + 2C35C4A61F4DA3E9002F3BF4 /* RatingsAPI.swift in Sources */, 2C89AA0B1F4C289900227C3B /* DataConvertable.swift in Sources */, 2C89AA0C1F4C289900227C3B /* CoursePlainEntity.swift in Sources */, 2C89AA0D1F4C289900227C3B /* CourseMetainfoEntity.swift in Sources */, @@ -10483,6 +10626,7 @@ 2C89AA8F1F4C289900227C3B /* User+CoreDataProperties.swift in Sources */, 2C89AA901F4C289900227C3B /* User.swift in Sources */, 2C89AA911F4C289900227C3B /* CongratulationAlertManager.swift in Sources */, + 2C35C4B81F4DA415002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */, 2C89AA921F4C289900227C3B /* Parser.swift in Sources */, 2C89AA931F4C289900227C3B /* StepicToken.swift in Sources */, 2C89AA941F4C289900227C3B /* AuthInfo.swift in Sources */, @@ -10505,6 +10649,7 @@ 2C89AAA51F4C289900227C3B /* DevicesAPI.swift in Sources */, 2C89AAA61F4C289900227C3B /* APIDefaults.swift in Sources */, 2C89AAA71F4C289900227C3B /* AdaptiveOnboardingPresenter.swift in Sources */, + 2C35C4AC1F4DA3F1002F3BF4 /* LeaderboardTableViewCell.swift in Sources */, 2C89AAA81F4C289900227C3B /* DiscussionProxiesAPI.swift in Sources */, 2C89AAA91F4C289900227C3B /* SearchQueriesView.swift in Sources */, 2C89AAAA1F4C289900227C3B /* LastStepsAPI.swift in Sources */, @@ -10536,6 +10681,7 @@ 2C89AAC41F4C289900227C3B /* CodeSuggestionTableViewCell.swift in Sources */, 2C89AAC51F4C289900227C3B /* PreferencesContainer.swift in Sources */, 2C89AAC61F4C289900227C3B /* NotificationPreferencesContainer.swift in Sources */, + 2C35C4BE1F4DA41B002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */, 2C89AAC71F4C289900227C3B /* LocalNotificationManager.swift in Sources */, 2C89AAC81F4C289900227C3B /* UIColorExtensions.swift in Sources */, 2C89AAC91F4C289900227C3B /* StandardsExtensions.swift in Sources */, @@ -10751,6 +10897,7 @@ 2C9731BC1F4C38F600AC9301 /* StepReversedCardView.swift in Sources */, 2C9731BD1F4C38F600AC9301 /* WatchSessionDataObserver.swift in Sources */, 2C9731BE1F4C38F600AC9301 /* WatchSessionSender.swift in Sources */, + 2C35C4A71F4DA3EA002F3BF4 /* RatingsAPI.swift in Sources */, 2C9731BF1F4C38F600AC9301 /* DataConvertable.swift in Sources */, 2C9731C01F4C38F600AC9301 /* CoursePlainEntity.swift in Sources */, 2C9731C11F4C38F600AC9301 /* CourseMetainfoEntity.swift in Sources */, @@ -10887,6 +11034,7 @@ 2C9732431F4C38F600AC9301 /* User+CoreDataProperties.swift in Sources */, 2C9732441F4C38F600AC9301 /* User.swift in Sources */, 2C9732451F4C38F600AC9301 /* CongratulationAlertManager.swift in Sources */, + 2C35C4B91F4DA416002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */, 2C9732461F4C38F600AC9301 /* Parser.swift in Sources */, 2C9732471F4C38F600AC9301 /* StepicToken.swift in Sources */, 2C9732481F4C38F600AC9301 /* AuthInfo.swift in Sources */, @@ -10909,6 +11057,7 @@ 2C9732591F4C38F600AC9301 /* DevicesAPI.swift in Sources */, 2C97325A1F4C38F600AC9301 /* APIDefaults.swift in Sources */, 2C97325B1F4C38F600AC9301 /* AdaptiveOnboardingPresenter.swift in Sources */, + 2C35C4AE1F4DA3F1002F3BF4 /* LeaderboardTableViewCell.swift in Sources */, 2C97325C1F4C38F600AC9301 /* DiscussionProxiesAPI.swift in Sources */, 2C97325D1F4C38F600AC9301 /* SearchQueriesView.swift in Sources */, 2C97325E1F4C38F600AC9301 /* LastStepsAPI.swift in Sources */, @@ -10940,6 +11089,7 @@ 2C9732781F4C38F600AC9301 /* CodeSuggestionTableViewCell.swift in Sources */, 2C9732791F4C38F600AC9301 /* PreferencesContainer.swift in Sources */, 2C97327A1F4C38F600AC9301 /* NotificationPreferencesContainer.swift in Sources */, + 2C35C4BF1F4DA41C002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */, 2C97327B1F4C38F600AC9301 /* LocalNotificationManager.swift in Sources */, 2C97327C1F4C38F600AC9301 /* UIColorExtensions.swift in Sources */, 2C97327D1F4C38F600AC9301 /* StandardsExtensions.swift in Sources */, @@ -11155,6 +11305,7 @@ 8618E3E41EA0063D00FB3D0F /* StepReversedCardView.swift in Sources */, 864D67361E83DE76001E8D9E /* WatchSessionDataObserver.swift in Sources */, 864D67371E83DE76001E8D9E /* WatchSessionSender.swift in Sources */, + 2C35C4A41F4DA3E8002F3BF4 /* RatingsAPI.swift in Sources */, 864D67381E83DE76001E8D9E /* DataConvertable.swift in Sources */, 864D67391E83DE76001E8D9E /* CoursePlainEntity.swift in Sources */, 864D673A1E83DE76001E8D9E /* CourseMetainfoEntity.swift in Sources */, @@ -11291,6 +11442,7 @@ 864D66FC1E83DE03001E8D9E /* User+CoreDataProperties.swift in Sources */, 864D66FD1E83DE03001E8D9E /* User.swift in Sources */, 2C6ABA631F1E00B5005A3BCF /* CongratulationAlertManager.swift in Sources */, + 2C35C4B61F4DA411002F3BF4 /* AdaptiveAchievementsPresenter.swift in Sources */, 864D66FE1E83DE03001E8D9E /* Parser.swift in Sources */, 864D66FF1E83DE03001E8D9E /* StepicToken.swift in Sources */, 864D67001E83DE03001E8D9E /* AuthInfo.swift in Sources */, @@ -11313,6 +11465,7 @@ 864D670B1E83DE03001E8D9E /* DevicesAPI.swift in Sources */, 864D670C1E83DE03001E8D9E /* APIDefaults.swift in Sources */, 2CA50B9E1F0FE66B00418E7D /* AdaptiveOnboardingPresenter.swift in Sources */, + 2C35C4AA1F4DA3EF002F3BF4 /* LeaderboardTableViewCell.swift in Sources */, 864D670D1E83DE03001E8D9E /* DiscussionProxiesAPI.swift in Sources */, 08E8F96E1F34DD48008CF4A1 /* SearchQueriesView.swift in Sources */, 08E3A3B81E93C22200E9C2EF /* LastStepsAPI.swift in Sources */, @@ -11344,6 +11497,7 @@ 085E9E6F1F138C3800D6A4BC /* CodeSuggestionTableViewCell.swift in Sources */, 864D67231E83DE03001E8D9E /* PreferencesContainer.swift in Sources */, 864D67241E83DE03001E8D9E /* NotificationPreferencesContainer.swift in Sources */, + 2C35C4BC1F4DA41A002F3BF4 /* AdaptiveRatingsPresenter.swift in Sources */, 864D67251E83DE03001E8D9E /* LocalNotificationManager.swift in Sources */, 864D67261E83DE03001E8D9E /* UIColorExtensions.swift in Sources */, 864D67271E83DE03001E8D9E /* StandardsExtensions.swift in Sources */, @@ -11477,6 +11631,42 @@ path = OnboardingContent; sourceTree = ""; }; + 2C35C4C51F4DA462002F3BF4 /* adjectives_f.plist */ = { + isa = PBXVariantGroup; + children = ( + 2C35C4C61F4DA462002F3BF4 /* ru */, + 2C35C4D11F4DA467002F3BF4 /* en */, + ); + name = adjectives_f.plist; + sourceTree = ""; + }; + 2C35C4C71F4DA462002F3BF4 /* adjectives_m.plist */ = { + isa = PBXVariantGroup; + children = ( + 2C35C4C81F4DA462002F3BF4 /* ru */, + 2C35C4D21F4DA46A002F3BF4 /* en */, + ); + name = adjectives_m.plist; + sourceTree = ""; + }; + 2C35C4C91F4DA462002F3BF4 /* nouns_f.plist */ = { + isa = PBXVariantGroup; + children = ( + 2C35C4CA1F4DA462002F3BF4 /* ru */, + 2C35C4D31F4DA46E002F3BF4 /* en */, + ); + name = nouns_f.plist; + sourceTree = ""; + }; + 2C35C4CB1F4DA462002F3BF4 /* nouns_m.plist */ = { + isa = PBXVariantGroup; + children = ( + 2C35C4CC1F4DA462002F3BF4 /* ru */, + 2C35C4D41F4DA471002F3BF4 /* en */, + ); + name = nouns_m.plist; + sourceTree = ""; + }; 2C6640DD1F30BCCB0033A274 /* ProgressTableViewCell.xib */ = { isa = PBXVariantGroup; children = ( diff --git a/Stepic/ApplicationInfo.swift b/Stepic/ApplicationInfo.swift index b6c73bd4bd..2592ea052c 100644 --- a/Stepic/ApplicationInfo.swift +++ b/Stepic/ApplicationInfo.swift @@ -30,6 +30,7 @@ class ApplicationInfo { static let isAdaptive = "adaptive.isAdaptive" static let courseId = "adaptive.courseId" static let mainColor = "adaptive.mainColor" + static let ratingURL = "adaptive.ratingURL" } struct RateApp { static let submissionsThreshold = "rateApp.submissionsThreshold" diff --git a/Stepic/QuizViewController.swift b/Stepic/QuizViewController.swift index 4bbe2bb5b5..67b3b01a77 100644 --- a/Stepic/QuizViewController.swift +++ b/Stepic/QuizViewController.swift @@ -290,8 +290,8 @@ class QuizViewController: UIViewController, QuizView, QuizControllerDataSource { guard let encodedUrl = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: encodedUrl) - else { - return + else { + return } WebControllerManager.sharedManager.presentWebControllerWithURL(url, inController: self, withKey: "external link", allowsSafari: true, backButtonStyle: BackButtonStyle.close) diff --git a/Stepic/StepicApplicationsInfo.swift b/Stepic/StepicApplicationsInfo.swift index ee577922f1..241b9a629a 100644 --- a/Stepic/StepicApplicationsInfo.swift +++ b/Stepic/StepicApplicationsInfo.swift @@ -59,6 +59,7 @@ struct StepicApplicationsInfo { static let isAdaptive = StepicApplicationsInfo.stepikConfigDic?.get(for: Root.Adaptive.isAdaptive) as? Bool ?? false static let adaptiveCourseId = StepicApplicationsInfo.stepikConfigDic?.get(for: Root.Adaptive.courseId) as? Int ?? 0 static let adaptiveMainColor = UIColor(hex: StepicApplicationsInfo.stepikConfigDic?.get(for: Root.Adaptive.mainColor) as? Int ?? 6736998) + static let adaptiveRatingURL = StepicApplicationsInfo.stepikConfigDic?.get(for: Root.Adaptive.ratingURL) as? String ?? "" // Section: RateApp struct RateApp { diff --git a/Stepic/en.lproj/Localizable.strings b/Stepic/en.lproj/Localizable.strings index 2c2465497e..b54599cf96 100644 --- a/Stepic/en.lproj/Localizable.strings +++ b/Stepic/en.lproj/Localizable.strings @@ -259,6 +259,11 @@ RetentionNotificationYesterdayZero = "You didn't score any points for yesterday. RetentionNotificationYesterdayStreak = "You have been learning %@ in a row. Go back and keep learning!"; RetentionNotificationWeekly = "You haven't done it for a long time. Go back and keep learning!"; SocialSignupWithExistingEmail = "This email is already used. To log in using this social account you have to log in via email and password"; +AdaptiveRatingFooterText1 = "%@ user in the table"; +AdaptiveRatingFooterText234 = "%@ users in the table"; +AdaptiveRatingFooterText567890 = "%@ users in the table"; +AdaptiveRatingYou = "You"; +AdaptiveRatingLoadError = "Rating is unavailable now"; AdaptiveAchievementFirstStep = "First steps"; AdaptiveAchievementFirstStepDesc = "Complete tutorial"; AdaptiveAchievementShare = "Sociable"; diff --git a/Stepic/ru.lproj/Localizable.strings b/Stepic/ru.lproj/Localizable.strings index 28858157f0..90e065a234 100644 --- a/Stepic/ru.lproj/Localizable.strings +++ b/Stepic/ru.lproj/Localizable.strings @@ -260,6 +260,11 @@ RetentionNotificationYesterdayZero = "За вчерашний день Вы ни RetentionNotificationYesterdayStreak = "Вы занимаетесь уже %@ подряд. Возвращайтесь и продолжайте учиться!"; RetentionNotificationWeekly = "Вы давно не занимались. Возвращайтесь и продолжайте учиться!"; SocialSignupWithExistingEmail = "Указанный email привязан к другому аккаунту. Чтобы входить через эту социальную сеть, необходимо войти, используя email и пароль"; +AdaptiveRatingFooterText1 = "%@ пользователь в рейтинге"; +AdaptiveRatingFooterText234 = "%@ пользователя в рейтинге"; +AdaptiveRatingFooterText567890 = "%@ пользователей в рейтинге"; +AdaptiveRatingYou = "Вы"; +AdaptiveRatingLoadError = "Рейтинг недоступен"; AdaptiveAchievementFirstStep = "Первые шаги"; AdaptiveAchievementFirstStepDesc = "Пройти обучение"; AdaptiveAchievementShare = "Общительный"; diff --git a/StepicAdaptiveCourse/AdaptiveAchievementsPresenter.swift b/StepicAdaptiveCourse/AdaptiveAchievementsPresenter.swift new file mode 100644 index 0000000000..d05f68c6fc --- /dev/null +++ b/StepicAdaptiveCourse/AdaptiveAchievementsPresenter.swift @@ -0,0 +1,51 @@ +// +// AdaptiveAchievementsPresenter.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 15.08.2017. +// Copyright © 2017 Alex Karpov. All rights reserved. +// + +import Foundation + +protocol AdaptiveAchievementsView: class { + func reload() + func setAchievements(records: [AchievementViewData]) +} + +struct AchievementViewData { + let name: String + let info: String + let type: AchievementType + let cover: UIImage? + let isUnlocked: Bool + let currentProgress: Int + let maxProgress: Int +} + +class AdaptiveAchievementsPresenter { + weak var view: AdaptiveAchievementsView? + + fileprivate var achievementsManager: AchievementManager + + private var achievements: [AchievementViewData]? + + init(achievementsManager: AchievementManager, view: AdaptiveAchievementsView) { + self.view = view + + self.achievementsManager = achievementsManager + } + + func reloadData(force: Bool = false) { + if achievements == nil || force { + achievements = [] + achievementsManager.storedAchievements.forEach({ achievement in + achievements!.append(AchievementViewData(name: achievement.name, info: achievement.info ?? "", type: achievement.type, cover: achievement.cover, isUnlocked: achievement.isUnlocked, currentProgress: achievement.progressValue, maxProgress: achievement.maxProgressValue)) + }) + } + + view?.setAchievements(records: achievements!) + + view?.reload() + } +} diff --git a/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal1.imageset/Contents.json b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal1.imageset/Contents.json new file mode 100644 index 0000000000..3b96c63c8a --- /dev/null +++ b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "medal3@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal1.imageset/medal3@2x.png b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal1.imageset/medal3@2x.png new file mode 100644 index 0000000000..92b8d5ae2d Binary files /dev/null and b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal1.imageset/medal3@2x.png differ diff --git a/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal2.imageset/Contents.json b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal2.imageset/Contents.json new file mode 100644 index 0000000000..d51fc477b4 --- /dev/null +++ b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "medal2@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal2.imageset/medal2@2x.png b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal2.imageset/medal2@2x.png new file mode 100644 index 0000000000..ecf9f8688e Binary files /dev/null and b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal2.imageset/medal2@2x.png differ diff --git a/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal3.imageset/Contents.json b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal3.imageset/Contents.json new file mode 100644 index 0000000000..80f932d0af --- /dev/null +++ b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "medal1@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal3.imageset/medal1@2x.png b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal3.imageset/medal1@2x.png new file mode 100644 index 0000000000..d2dccef88c Binary files /dev/null and b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/medal3.imageset/medal1@2x.png differ diff --git a/StepicAdaptiveCourse/AdaptiveAssets.xcassets/more.imageset/Contents.json b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/more.imageset/Contents.json new file mode 100644 index 0000000000..5fead88de4 --- /dev/null +++ b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/more.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "more@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/StepicAdaptiveCourse/AdaptiveAssets.xcassets/more.imageset/more@2x.png b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/more.imageset/more@2x.png new file mode 100644 index 0000000000..05f1e870df Binary files /dev/null and b/StepicAdaptiveCourse/AdaptiveAssets.xcassets/more.imageset/more@2x.png differ diff --git a/StepicAdaptiveCourse/AdaptiveRatingsPresenter.swift b/StepicAdaptiveCourse/AdaptiveRatingsPresenter.swift new file mode 100644 index 0000000000..2519b6c9cf --- /dev/null +++ b/StepicAdaptiveCourse/AdaptiveRatingsPresenter.swift @@ -0,0 +1,134 @@ +// +// AdaptiveRatingsPresenter.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 15.08.2017. +// Copyright © 2017 Alex Karpov. All rights reserved. +// + +import Foundation +import Alamofire + +protocol AdaptiveRatingsView: class { + func reload() + func setRatings(data: ScoreboardViewData) + func showError() +} + +struct RatingViewData { + let position: Int + let exp: Int + let name: String + let me: Bool +} + +struct ScoreboardViewData { + let allCount: Int + let leaders: [RatingViewData] +} + +class AdaptiveRatingsPresenter { + weak var view: AdaptiveRatingsView? + + fileprivate var ratingsAPI: RatingsAPI + fileprivate var ratingManager: RatingManager + + private var scoreboard: [Int: ScoreboardViewData] = [:] + + private var currentRequest: Request? + + // Names (word + grammatical gender) + private var nouns: [(String, String)] = [] + private var adjs: [(String, String)] = [] + + init(ratingsAPI: RatingsAPI, ratingManager: RatingManager, view: AdaptiveRatingsView) { + self.view = view + self.ratingManager = ratingManager + self.ratingsAPI = ratingsAPI + + loadNamesFromFiles() + } + + func reloadData(days: Int? = nil, force: Bool = false) { + // Send rating first, then get rating + currentRequest?.cancel() + currentRequest = ratingsAPI.update(courseId: StepicApplicationsInfo.adaptiveCourseId, exp: ratingManager.rating, success: { _ in + print("remote rating updated -> reload rating") + self.reloadRating(days: days, force: force) + }, error: { responseStatus in + switch responseStatus { + case .serverError: + print("remote rating update failed: server error") + AnalyticsReporter.reportEvent(AnalyticsEvents.Adaptive.ratingServerError) + case .connectionError(let error): + print("remote rating update failed: \(error)") + default: + print("remote rating update failed: \(responseStatus)") + } + self.view?.setRatings(data: ScoreboardViewData(allCount: 0, leaders: [])) + self.view?.reload() + self.view?.showError() + }) + } + + fileprivate func reloadRating(days: Int? = nil, force: Bool = false) { + let downloadedScoreboard = scoreboard[days ?? 0] // 0 when 'days' == nil + if downloadedScoreboard == nil || force { + let currentUser = AuthInfo.shared.userId + + currentRequest?.cancel() + currentRequest = ratingsAPI.retrieve(courseId: StepicApplicationsInfo.adaptiveCourseId, count: 10, days: days, success: { scoreboard in + var curLeaders: [RatingViewData] = [] + scoreboard.leaders.forEach { record in + curLeaders.append(RatingViewData(position: record.rank, exp: record.exp, name: self.generateNameBy(userId: record.userId), me: currentUser == record.userId)) + } + + let curScoreboard = ScoreboardViewData(allCount: scoreboard.allCount, leaders: curLeaders) + self.scoreboard[days ?? 0] = curScoreboard + self.view?.setRatings(data: curScoreboard) + self.view?.reload() + }, error: { err in + print(err) + self.view?.setRatings(data: ScoreboardViewData(allCount: 0, leaders: [])) + self.view?.reload() + self.view?.showError() + }) + } else { + view?.setRatings(data: downloadedScoreboard!) + view?.reload() + } + } + + fileprivate func loadNamesFromFiles() { + func readFile(name: String) -> [String] { + if let path = Bundle.main.path(forResource: name, ofType: "plist"), + let words = NSArray(contentsOfFile: path) as? [String] { + return words + } + return [] + } + + readFile(name: "adjectives_m").forEach { adjs.append(($0, "m")) } + readFile(name: "adjectives_f").forEach { adjs.append(($0, "f")) } + readFile(name: "nouns_m").forEach { nouns.append(($0, "m")) } + readFile(name: "nouns_f").forEach { nouns.append(($0, "f")) } + + assert(adjs.count % 2 == 0) + } + + fileprivate func generateNameBy(userId: Int) -> String { + func hash(_ id: Int) -> Int { + var x = id + x = ((x >> 16) ^ x) &* 0x45d9f3b + x = ((x >> 16) ^ x) &* 0x45d9f3b + x = (x >> 16) ^ x + return x % (nouns.count * (adjs.count / 2)) + } + + let noun = nouns[hash(userId) % nouns.count] + let adjsByGender = adjs.flatMap { noun.1 == $0.1 ? $0 : nil } + let adjNum = hash(userId) / nouns.count + + return "\(adjsByGender[adjNum].0.capitalized) \(noun.0)" + } +} diff --git a/StepicAdaptiveCourse/AdaptiveStatsPresenter.swift b/StepicAdaptiveCourse/AdaptiveStatsPresenter.swift index 019b47e5ee..c9056f89f1 100644 --- a/StepicAdaptiveCourse/AdaptiveStatsPresenter.swift +++ b/StepicAdaptiveCourse/AdaptiveStatsPresenter.swift @@ -11,7 +11,6 @@ import Foundation protocol AdaptiveStatsView: class { func reload() func setProgress(records: [WeekProgressViewData]) - func setAchievements(records: [AchievementViewData]) func setGeneralStats(currentLevel: Int, bestStreak: Int, currentWeekXP: Int, last7DaysProgress: [Int]?) } @@ -21,97 +20,79 @@ struct WeekProgressViewData { let isRecord: Bool } -struct AchievementViewData { - let name: String - let info: String - let type: AchievementType - let cover: UIImage? - let isUnlocked: Bool - let currentProgress: Int - let maxProgress: Int -} - class AdaptiveStatsPresenter { weak var view: AdaptiveStatsView? fileprivate var ratingManager: RatingManager fileprivate var statsManager: StatsManager - fileprivate var achievementsManager: AchievementManager - init(statsManager: StatsManager, ratingManager: RatingManager, achievementsManager: AchievementManager, view: AdaptiveStatsView) { + private var progressByWeek: [WeekProgressViewData]? + + init(statsManager: StatsManager, ratingManager: RatingManager, view: AdaptiveStatsView) { self.view = view self.statsManager = statsManager self.ratingManager = ratingManager - self.achievementsManager = achievementsManager } func reloadStats() { - var achievements: [AchievementViewData] = [] - var progressByWeek: [WeekProgressViewData] = [] - let currentXP = ratingManager.rating let currentLevel = RatingHelper.getLevel(for: currentXP) let bestStreak = max(1, statsManager.maxStreak) - // Achievements - achievementsManager.storedAchievements.forEach({ achievement in - achievements.append(AchievementViewData(name: achievement.name, info: achievement.info ?? "", type: achievement.type, cover: achievement.cover, isUnlocked: achievement.isUnlocked, currentProgress: achievement.progressValue, maxProgress: achievement.maxProgressValue)) - }) - - view?.setAchievements(records: achievements) - - guard let stats = statsManager.stats else { - view?.setGeneralStats(currentLevel: currentLevel, bestStreak: bestStreak, currentWeekXP: 0, last7DaysProgress: nil) - view?.reload() - return - } - let last7DaysProgress = statsManager.getLastDays(count: 7) - var currentWeekXP = last7DaysProgress.reduce(0, +) + let currentWeekXP = last7DaysProgress.reduce(0, +) view?.setGeneralStats(currentLevel: currentLevel, bestStreak: bestStreak, currentWeekXP: currentWeekXP, last7DaysProgress: last7DaysProgress) + } - // Calculate progress by week - func getWeekBeginByDate(_ date: Date) -> Date { - let calendar = Calendar(identifier: .gregorian) - return calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: date))! - } + func reloadData(force: Bool = false) { + if progressByWeek == nil || force { + progressByWeek = [] - // Empty stats - if stats.first == nil { - return - } + guard let stats = statsManager.stats else { + return + } + + // Calculate progress by week + func getWeekBeginByDate(_ date: Date) -> Date { + let calendar = Calendar(identifier: .gregorian) + return calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: date))! + } + + // Empty stats + if stats.first == nil { + return + } - var weekXP: [Int: Int] = [:] - var weeks: Set = Set() - var weekRecordBeginHash: Int? = nil - for (day, progress) in stats { - let weekBeginForCurrentDay = getWeekBeginByDate(statsManager.dateByDay(day)) - let weekHash = weekBeginForCurrentDay.hashValue - weeks.insert(weekBeginForCurrentDay) + var weekXP: [Int: Int] = [:] + var weeks: Set = Set() + var weekRecordBeginHash: Int? = nil + for (day, progress) in stats { + let weekBeginForCurrentDay = getWeekBeginByDate(statsManager.dateByDay(day)) + let weekHash = weekBeginForCurrentDay.hashValue + weeks.insert(weekBeginForCurrentDay) + + if weekXP[weekHash] == nil { + if weekRecordBeginHash == nil { + weekRecordBeginHash = weekHash + } + weekXP[weekHash] = progress + } else { + weekXP[weekHash]! += progress + } - if weekXP[weekHash] == nil { - if weekRecordBeginHash == nil { + if weekXP[weekRecordBeginHash!]! < weekXP[weekHash]! { weekRecordBeginHash = weekHash } - weekXP[weekHash] = progress - } else { - weekXP[weekHash]! += progress } - if weekXP[weekRecordBeginHash!]! < weekXP[weekHash]! { - weekRecordBeginHash = weekHash + for firstDayOfWeek in weeks { + progressByWeek!.append(WeekProgressViewData(weekBegin: firstDayOfWeek, progress: weekXP[firstDayOfWeek.hashValue] ?? 0, isRecord: firstDayOfWeek.hashValue == weekRecordBeginHash && weeks.count > 1)) } } - for firstDayOfWeek in weeks { - progressByWeek.append(WeekProgressViewData(weekBegin: firstDayOfWeek, progress: weekXP[firstDayOfWeek.hashValue] ?? 0, isRecord: firstDayOfWeek.hashValue == weekRecordBeginHash && weeks.count > 1)) - } - - view?.setProgress(records: progressByWeek.reversed()) - + view?.setProgress(records: progressByWeek!.reversed()) view?.reload() } - } diff --git a/StepicAdaptiveCourse/AdaptiveStatsViewController.swift b/StepicAdaptiveCourse/AdaptiveStatsViewController.swift index fca2fd7d37..efbb6df1cd 100644 --- a/StepicAdaptiveCourse/AdaptiveStatsViewController.swift +++ b/StepicAdaptiveCourse/AdaptiveStatsViewController.swift @@ -9,21 +9,83 @@ import UIKit import Charts -class AdaptiveStatsViewController: UIViewController, AdaptiveStatsView { - var presenter: AdaptiveStatsPresenter? +class OverlapTableView: UITableView { + private var previousCellsStateHash: Int = 0 - @IBOutlet weak var tableView: UITableView! + override func layoutSubviews() { + super.layoutSubviews() + + guard let wrapper = subviews.first else { + return + } + + let currentHash = visibleCells.description.hashValue + if currentHash != previousCellsStateHash { + visibleCells.reversed().forEach { wrapper.bringSubview(toFront: $0) } + previousCellsStateHash = currentHash + } + } +} + +class AdaptiveStatsViewController: UIViewController { + enum State { + case progress + case achievements + case ratings(days: Int?) + } + + fileprivate var state: State = .progress { + didSet { + allCountLabel.isHidden = true + loadingIndicator.startAnimating() + switch state { + case .progress: + statsPresenter?.reloadData(force: data == nil) + ratingSegmentedControl.isHidden = true + break + case .achievements: + achievementsPresenter?.reloadData(force: data == nil) + ratingSegmentedControl.isHidden = true + break + case .ratings(let days): + ratingsPresenter?.reloadData(days: days, force: data == nil) + ratingSegmentedControl.isHidden = false + } + } + } + + var statsPresenter: AdaptiveStatsPresenter? + var achievementsPresenter: AdaptiveAchievementsPresenter? + var ratingsPresenter: AdaptiveRatingsPresenter? + + @IBOutlet weak var tableView: OverlapTableView! @IBOutlet weak var progressChart: LineChartView! - @IBOutlet weak var segmentedControl: UISegmentedControl! @IBOutlet weak var currentWeekXPLabel: UILabel! @IBOutlet weak var bestStreakLabel: UILabel! @IBOutlet weak var currentLevelLabel: UILabel! + @IBOutlet weak var allCountLabel: UILabel! + @IBOutlet weak var loadingIndicator: UIActivityIndicatorView! + + @IBOutlet weak var ratingSegmentedControl: UISegmentedControl! + @IBOutlet weak var segmentedControl: UISegmentedControl! + + fileprivate var data: [Any]? - fileprivate var achievements: [AchievementViewData] = [] - fileprivate var progressByWeek: [WeekProgressViewData] = [] + @IBAction func onRatingSegmentedControlValueChanged(_ sender: Any) { + if ratingSegmentedControl.selectedSegmentIndex == 0 { + state = .ratings(days: nil) + } else { + state = .ratings(days: 7) + } + } @IBAction func onSegmentedControlValueChanged(_ sender: Any) { - tableView.reloadData() + let states: [Int: State] = [ + 0: .progress, + 1: .achievements, + 2: .ratings(days: 7) + ] + state = states[segmentedControl.selectedSegmentIndex] ?? .progress } @IBAction func onCancelButtonClick(_ sender: Any) { @@ -33,7 +95,9 @@ class AdaptiveStatsViewController: UIViewController, AdaptiveStatsView { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - presenter = AdaptiveStatsPresenter(statsManager: StatsManager.shared, ratingManager: RatingManager.shared, achievementsManager: AchievementManager.shared, view: self) + statsPresenter = AdaptiveStatsPresenter(statsManager: StatsManager.shared, ratingManager: RatingManager.shared, view: self) + achievementsPresenter = AdaptiveAchievementsPresenter(achievementsManager: AchievementManager.shared, view: self) + ratingsPresenter = AdaptiveRatingsPresenter(ratingsAPI: ApiDataDownloader.adaptiveRatings, ratingManager: RatingManager.shared, view: self) } override func viewDidLoad() { @@ -44,7 +108,10 @@ class AdaptiveStatsViewController: UIViewController, AdaptiveStatsView { setUpTable() setUpChart() - presenter?.reloadStats() + statsPresenter?.reloadStats() + + // Default state + state = .progress } fileprivate func colorize() { @@ -52,39 +119,34 @@ class AdaptiveStatsViewController: UIViewController, AdaptiveStatsView { bestStreakLabel.textColor = StepicApplicationsInfo.adaptiveMainColor currentLevelLabel.textColor = StepicApplicationsInfo.adaptiveMainColor segmentedControl.tintColor = StepicApplicationsInfo.adaptiveMainColor + loadingIndicator.color = StepicApplicationsInfo.adaptiveMainColor + ratingSegmentedControl.tintColor = StepicApplicationsInfo.adaptiveMainColor navigationItem.leftBarButtonItem?.tintColor = StepicApplicationsInfo.adaptiveMainColor navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: StepicApplicationsInfo.adaptiveMainColor] } - func reload() { - tableView.delegate = self - tableView.dataSource = self - - tableView.reloadData() - } + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() - func setProgress(records: [WeekProgressViewData]) { - progressByWeek = records.reversed() - } + guard let headerView = tableView.tableHeaderView else { + return + } - func setAchievements(records: [AchievementViewData]) { - achievements = records + let size = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize) + if headerView.frame.size.height != size.height { + headerView.frame.size.height = size.height + tableView.tableHeaderView = headerView + tableView.layoutIfNeeded() + } } - func setGeneralStats(currentLevel: Int, bestStreak: Int, currentWeekXP: Int, last7DaysProgress: [Int]?) { - currentLevelLabel.text = "\(currentLevel)" - bestStreakLabel.text = "\(bestStreak)" - currentWeekXPLabel.text = "\(currentWeekXP)" + func reload() { + loadingIndicator.stopAnimating() - guard let last7DaysProgress = last7DaysProgress else { - return - } + tableView.delegate = self + tableView.dataSource = self - let dataSet = updateDataSet(LineChartDataSet(values: valuesToDataEntries(values: last7DaysProgress.reversed()), label: "")) - let data = LineChartData(dataSet: dataSet) - progressChart.data = data - progressChart.data?.highlightEnabled = true - progressChart.animate(yAxisDuration: 1.4, easingOption: .easeInOutCirc) + tableView.reloadData() } fileprivate func valuesToDataEntries(values: [Int]) -> [ChartDataEntry] { @@ -104,6 +166,7 @@ class AdaptiveStatsViewController: UIViewController, AdaptiveStatsView { tableView.register(UINib(nibName: "ProgressTableViewCell", bundle: nil), forCellReuseIdentifier: ProgressTableViewCell.reuseId) tableView.register(UINib(nibName: "AchievementTableViewCell", bundle: nil), forCellReuseIdentifier: AchievementTableViewCell.reuseId) + tableView.register(UINib(nibName: "LeaderboardTableViewCell", bundle: nil), forCellReuseIdentifier: LeaderboardTableViewCell.reuseId) } fileprivate func setUpChart() { @@ -138,28 +201,113 @@ class AdaptiveStatsViewController: UIViewController, AdaptiveStatsView { } } -extension AdaptiveStatsViewController: UITableViewDelegate, UITableViewDataSource { - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if segmentedControl.selectedSegmentIndex == 0 { - return progressByWeek.count - } else { - return achievements.count +extension AdaptiveStatsViewController: AdaptiveRatingsView { + func setRatings(data: ScoreboardViewData) { + self.data = data.leaders + + let pluralizedString = StringHelper.pluralize(number: data.allCount, forms: [ + NSLocalizedString("AdaptiveRatingFooterText1", comment: ""), + NSLocalizedString("AdaptiveRatingFooterText234", comment: ""), + NSLocalizedString("AdaptiveRatingFooterText567890", comment: "") + ]) + allCountLabel.text = String(format: pluralizedString, "\(data.allCount)") + allCountLabel.isHidden = false + } + + func showError() { + allCountLabel.text = NSLocalizedString("AdaptiveRatingLoadError", comment: "") + allCountLabel.isHidden = false + } + + var separatorPosition: Int? { + guard let data = data as? [RatingViewData] else { + return nil + } + + switch state { + case .ratings(_): + for i in 0.. Int { + return(data?.count ?? 0) + (separatorPosition != nil ? 1 : 0) } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - if segmentedControl.selectedSegmentIndex == 0 { + switch state { + case .progress: let cell = tableView.dequeueReusableCell(withIdentifier: ProgressTableViewCell.reuseId, for: indexPath) as! ProgressTableViewCell - let weekProgress = progressByWeek[indexPath.item] - cell.updateInfo(expCount: weekProgress.progress, begin: weekProgress.weekBegin, end: weekProgress.weekBegin.addingTimeInterval(6 * 24 * 60 * 60), isRecord: weekProgress.isRecord) + if let weekProgress = data?[indexPath.item] as? WeekProgressViewData { + cell.updateInfo(expCount: weekProgress.progress, begin: weekProgress.weekBegin, end: weekProgress.weekBegin.addingTimeInterval(6 * 24 * 60 * 60), isRecord: weekProgress.isRecord) + } return cell - } else { + case .achievements: let cell = tableView.dequeueReusableCell(withIdentifier: AchievementTableViewCell.reuseId, for: indexPath) as! AchievementTableViewCell - let achievement = achievements[indexPath.item] - cell.updateInfo(name: achievement.name, info: achievement.info, cover: achievement.cover, isUnlocked: achievement.isUnlocked, type: achievement.type, currentProgress: achievement.currentProgress, maxProgress: achievement.maxProgress) + if let achievement = data?[indexPath.item] as? AchievementViewData { + cell.updateInfo(name: achievement.name, info: achievement.info, cover: achievement.cover, isUnlocked: achievement.isUnlocked, type: achievement.type, currentProgress: achievement.currentProgress, maxProgress: achievement.maxProgress) + } + return cell + case .ratings(_): + let cell = tableView.dequeueReusableCell(withIdentifier: LeaderboardTableViewCell.reuseId, for: indexPath) as! LeaderboardTableViewCell + + let separatorAfterIndex = (separatorPosition ?? Int.max - 1) + + if separatorAfterIndex + 1 == indexPath.item { + cell.cellPosition = .separator + } else { + let dataIndex = separatorAfterIndex < indexPath.item ? indexPath.item - 1 : indexPath.item + + if let user = data?[dataIndex] as? RatingViewData { + cell.cellPosition = indexPath.item == tableView.numberOfRows(inSection: indexPath.section) - 1 ? .bottom : (indexPath.item == 0 ? .top : .middle) + + if dataIndex == separatorAfterIndex { + cell.cellPosition = .bottom + } else if dataIndex - 1 == separatorAfterIndex { + cell.cellPosition = .top + } + + cell.updateInfo(position: user.position, username: user.name, exp: user.exp, isMe: user.me) + } + } return cell } } - } diff --git a/StepicAdaptiveCourse/AdaptiveStepPresenter.swift b/StepicAdaptiveCourse/AdaptiveStepPresenter.swift index c6908f1fe2..c483a90aa4 100644 --- a/StepicAdaptiveCourse/AdaptiveStepPresenter.swift +++ b/StepicAdaptiveCourse/AdaptiveStepPresenter.swift @@ -52,6 +52,7 @@ class AdaptiveStepPresenter { delegate?.contentLoadingDidFail() return } + quizViewController.step = step quizViewController.delegate = self quizViewController.needNewAttempt = true diff --git a/StepicAdaptiveCourse/AdaptiveStepsPresenter.swift b/StepicAdaptiveCourse/AdaptiveStepsPresenter.swift index e3fe0dac7f..10c6c5823e 100644 --- a/StepicAdaptiveCourse/AdaptiveStepsPresenter.swift +++ b/StepicAdaptiveCourse/AdaptiveStepsPresenter.swift @@ -52,6 +52,7 @@ class AdaptiveStepsPresenter { fileprivate var statsManager: StatsManager? fileprivate var achievementsManager: AchievementManager? fileprivate var defaultsStorageManager: DefaultsStorageManager? + fileprivate var ratingsAPI: RatingsAPI? var isKolodaPresented = false var isJoinedCourse = false @@ -82,14 +83,30 @@ class AdaptiveStepsPresenter { } } - var lastReaction: Reaction? + var lastReaction: Reaction? { + didSet { + if let curState = self.currentStepPresenter?.state, + let reaction = self.lastReaction { + self.sendReaction(reaction: lastReaction!, success: { _ in + // Analytics + switch reaction { + case .maybeLater: + AnalyticsReporter.reportEvent(AnalyticsEvents.Adaptive.Reaction.hard, parameters: ["status": curState.rawValue]) + case .neverAgain: + AnalyticsReporter.reportEvent(AnalyticsEvents.Adaptive.Reaction.easy, parameters: ["status": curState.rawValue]) + default: break + } + }) + } + } + } var course: Course? var currentLesson: Lesson? var recommendedLessons: [Lesson] = [] var step: Step? - init(coursesAPI: CoursesAPI, stepsAPI: StepsAPI, lessonsAPI: LessonsAPI, progressesAPI: ProgressesAPI, stepicsAPI: StepicsAPI, recommendationsAPI: RecommendationsAPI, unitsAPI: UnitsAPI, viewsAPI: ViewsAPI, profilesAPI: ProfilesAPI, ratingManager: RatingManager, statsManager: StatsManager, achievementsManager: AchievementManager, defaultsStorageManager: DefaultsStorageManager, view: AdaptiveStepsView) { + init(coursesAPI: CoursesAPI, stepsAPI: StepsAPI, lessonsAPI: LessonsAPI, progressesAPI: ProgressesAPI, stepicsAPI: StepicsAPI, recommendationsAPI: RecommendationsAPI, unitsAPI: UnitsAPI, viewsAPI: ViewsAPI, profilesAPI: ProfilesAPI, ratingManager: RatingManager, statsManager: StatsManager, achievementsManager: AchievementManager, defaultsStorageManager: DefaultsStorageManager, ratingsAPI: RatingsAPI, view: AdaptiveStepsView) { self.coursesAPI = coursesAPI self.stepsAPI = stepsAPI self.lessonsAPI = lessonsAPI @@ -102,6 +119,7 @@ class AdaptiveStepsPresenter { self.ratingManager = ratingManager self.statsManager = statsManager self.defaultsStorageManager = defaultsStorageManager + self.ratingsAPI = ratingsAPI self.achievementsManager = achievementsManager self.achievementsManager?.delegate = self @@ -265,6 +283,7 @@ class AdaptiveStepsPresenter { performRequest({ self.recommendationsAPI?.sendRecommendationReaction(user: userId, lesson: lessonId, reaction: reaction, success: { + print("reaction sent: reaction = \(reaction)") success() }, error: { error in print("failed sending reaction: \(error)") @@ -506,6 +525,7 @@ class AdaptiveStepsPresenter { return } + // Override API let adaptiveStepPresenter = AdaptiveStepPresenter(view: stepViewController, step: step) adaptiveStepPresenter.delegate = self stepViewController.presenter = adaptiveStepPresenter @@ -526,54 +546,7 @@ class AdaptiveStepsPresenter { } else { // Next recommendation -> send reaction before print("last reaction: \((self?.lastReaction)!), getting new recommendation...") - - // Analytics - if let curState = self?.currentStepPresenter?.state, - let reaction = self?.lastReaction { - switch reaction { - case .maybeLater: - AnalyticsReporter.reportEvent(AnalyticsEvents.Adaptive.Reaction.hard, parameters: ["status": curState.rawValue]) - case .neverAgain: - AnalyticsReporter.reportEvent(AnalyticsEvents.Adaptive.Reaction.easy, parameters: ["status": curState.rawValue]) - default: break - } - } - - self?.sendReaction(reaction: (self?.lastReaction)!, success: { [weak self] in - guard let s = self else { return } - - // Update rating only after reaction was sent - if (s.currentStepPresenter?.state ?? .unsolved) == .successful { - let curStreak = s.streak - let curRating = s.rating - - let oldRating = curRating - let newRating = curRating + curStreak - s.rating += curStreak - - s.achievementsManager?.fireEvent(.exp(value: curStreak)) - s.achievementsManager?.fireEvent(.streak(value: curStreak)) - - // Update stats - s.statsManager?.incrementRating(curStreak) - s.statsManager?.maxStreak = curStreak - - // Days streak achievement - if !s.isSolvedToday { - s.isSolvedToday = true - s.achievementsManager?.fireEvent(.days(value: s.statsManager?.currentDayStreak ?? 1)) - } - - if RatingHelper.getLevel(for: oldRating) != RatingHelper.getLevel(for: newRating) { - s.achievementsManager?.fireEvent(.level(value: RatingHelper.getLevel(for: newRating))) - - s.view?.showCongratulationPopup(type: .level(level: RatingHelper.getLevel(for: newRating)), completion: nil) - } - s.streak += 1 - } - - s.getNewRecommendation(for: course, success: successHandler) - }) + self?.getNewRecommendation(for: course, success: successHandler) } } } @@ -610,9 +583,17 @@ extension AdaptiveStepsPresenter: StepCardViewDelegate { currentStepPresenter?.retry() break case .successful: - lastReaction = .solved view?.swipeCardUp() + // updated rating here + let newRating = rating + let oldRating = newRating - streak + 1 + if RatingHelper.getLevel(for: oldRating) != RatingHelper.getLevel(for: newRating) { + achievementsManager?.fireEvent(.level(value: RatingHelper.getLevel(for: newRating))) + + view?.showCongratulationPopup(type: .level(level: RatingHelper.getLevel(for: newRating)), completion: nil) + } + break } } @@ -630,13 +611,46 @@ extension AdaptiveStepsPresenter: AdaptiveStepDelegate { func stepSubmissionDidCorrect() { AnalyticsReporter.reportEvent(AnalyticsEvents.Adaptive.Step.correctAnswer) - // Update rating and streak - let newRating = rating + streak + lastReaction = .solved + + let curStreak = streak + let oldRating = rating + let newRating = oldRating + curStreak + achievementsManager?.fireEvent(.exp(value: curStreak)) + achievementsManager?.fireEvent(.streak(value: curStreak)) + + // Update stats + statsManager?.incrementRating(curStreak) + statsManager?.maxStreak = curStreak + + // Send rating + ratingsAPI?.update(courseId: StepicApplicationsInfo.adaptiveCourseId, exp: newRating, success: { _ in + print("remote rating updated") + }, error: { responseStatus in + switch responseStatus { + case .serverError: + print("remote rating update failed: server error") + AnalyticsReporter.reportEvent(AnalyticsEvents.Adaptive.ratingServerError) + case .connectionError(let error): + print("remote rating update failed: \(error)") + default: + print("remote rating update failed: \(responseStatus)") + } + }) + + // Days streak achievement + if !isSolvedToday { + isSolvedToday = true + achievementsManager?.fireEvent(.days(value: statsManager?.currentDayStreak ?? 1)) + } view?.showCongratulation(for: streak, isSpecial: streak > 1, completion: { self.view?.updateProgress(for: newRating) }) + rating = newRating + streak += 1 + view?.updateTopCardControl(stepState: .successful) } diff --git a/StepicAdaptiveCourse/AdaptiveStepsViewController.swift b/StepicAdaptiveCourse/AdaptiveStepsViewController.swift index 43a41a8fe7..572d8687fd 100644 --- a/StepicAdaptiveCourse/AdaptiveStepsViewController.swift +++ b/StepicAdaptiveCourse/AdaptiveStepsViewController.swift @@ -50,7 +50,7 @@ class AdaptiveStepsViewController: UIViewController, AdaptiveStepsView { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - presenter = AdaptiveStepsPresenter(coursesAPI: ApiDataDownloader.courses, stepsAPI: ApiDataDownloader.steps, lessonsAPI: ApiDataDownloader.lessons, progressesAPI: ApiDataDownloader.progresses, stepicsAPI: ApiDataDownloader.stepics, recommendationsAPI: ApiDataDownloader.recommendations, unitsAPI: ApiDataDownloader.units, viewsAPI: ApiDataDownloader.views, profilesAPI: ApiDataDownloader.profiles, ratingManager: RatingManager.shared, statsManager: StatsManager.shared, achievementsManager: AchievementManager.shared, defaultsStorageManager: DefaultsStorageManager.shared, view: self) + presenter = AdaptiveStepsPresenter(coursesAPI: ApiDataDownloader.courses, stepsAPI: ApiDataDownloader.steps, lessonsAPI: ApiDataDownloader.lessons, progressesAPI: ApiDataDownloader.progresses, stepicsAPI: ApiDataDownloader.stepics, recommendationsAPI: ApiDataDownloader.recommendations, unitsAPI: ApiDataDownloader.units, viewsAPI: ApiDataDownloader.views, profilesAPI: ApiDataDownloader.profiles, ratingManager: RatingManager.shared, statsManager: StatsManager.shared, achievementsManager: AchievementManager.shared, defaultsStorageManager: DefaultsStorageManager.shared, ratingsAPI: ApiDataDownloader.adaptiveRatings, view: self) } override func viewDidLoad() { diff --git a/StepicAdaptiveCourse/AnalyticsEvents+Adaptive.swift b/StepicAdaptiveCourse/AnalyticsEvents+Adaptive.swift index e6374c1681..a3b8800e0e 100644 --- a/StepicAdaptiveCourse/AnalyticsEvents+Adaptive.swift +++ b/StepicAdaptiveCourse/AnalyticsEvents+Adaptive.swift @@ -27,5 +27,6 @@ extension AnalyticsEvents { static let hard = "adaptive_reaction_hard" } static let localNotification = "adaptive_opened_by_local_notification" + static let ratingServerError = "adaptive_rating_server_error" } } diff --git a/StepicAdaptiveCourse/Base.lproj/AdaptiveMain.storyboard b/StepicAdaptiveCourse/Base.lproj/AdaptiveMain.storyboard index 139091d3ba..9f8fe21763 100644 --- a/StepicAdaptiveCourse/Base.lproj/AdaptiveMain.storyboard +++ b/StepicAdaptiveCourse/Base.lproj/AdaptiveMain.storyboard @@ -1,10 +1,10 @@ - + - + @@ -91,12 +91,13 @@ - + + - - + + @@ -238,17 +239,34 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + + - + + + + + + + + + + + + + + + + + @@ -298,10 +339,13 @@ + + + diff --git a/StepicAdaptiveCourse/Content/1838/Config.plist b/StepicAdaptiveCourse/Content/1838/Config.plist index 872aea3337..bb12d7b92b 100644 --- a/StepicAdaptiveCourse/Content/1838/Config.plist +++ b/StepicAdaptiveCourse/Content/1838/Config.plist @@ -35,6 +35,8 @@ adaptive + ratingURL + http://82.202.236.70:9000 mainColor 32896 isAdaptive diff --git a/StepicAdaptiveCourse/Content/1906/Config.plist b/StepicAdaptiveCourse/Content/1906/Config.plist index 4a2e4a64b1..37cd81191e 100644 --- a/StepicAdaptiveCourse/Content/1906/Config.plist +++ b/StepicAdaptiveCourse/Content/1906/Config.plist @@ -35,6 +35,8 @@ adaptive + ratingURL + http://82.202.236.70:9000 mainColor 3355494 isAdaptive diff --git a/StepicAdaptiveCourse/Content/3067/Config.plist b/StepicAdaptiveCourse/Content/3067/Config.plist index 1ee46936db..9d2fbbc760 100644 --- a/StepicAdaptiveCourse/Content/3067/Config.plist +++ b/StepicAdaptiveCourse/Content/3067/Config.plist @@ -35,6 +35,8 @@ adaptive + ratingURL + http://82.202.236.70:9000 mainColor 24987 isAdaptive diff --git a/StepicAdaptiveCourse/Content/3124/Config.plist b/StepicAdaptiveCourse/Content/3124/Config.plist index 908db55304..9a89928c36 100644 --- a/StepicAdaptiveCourse/Content/3124/Config.plist +++ b/StepicAdaptiveCourse/Content/3124/Config.plist @@ -35,6 +35,8 @@ adaptive + ratingURL + http://82.202.236.70:9000 mainColor 27826 isAdaptive diff --git a/StepicAdaptiveCourse/Content/3149/Config.plist b/StepicAdaptiveCourse/Content/3149/Config.plist index 66037a95d2..ca6618665b 100644 --- a/StepicAdaptiveCourse/Content/3149/Config.plist +++ b/StepicAdaptiveCourse/Content/3149/Config.plist @@ -35,6 +35,8 @@ adaptive + ratingURL + http://82.202.236.70:9000 mainColor 27826 isAdaptive diff --git a/StepicAdaptiveCourse/Content/3150/Config.plist b/StepicAdaptiveCourse/Content/3150/Config.plist index 46bdff867e..7471455c10 100644 --- a/StepicAdaptiveCourse/Content/3150/Config.plist +++ b/StepicAdaptiveCourse/Content/3150/Config.plist @@ -35,6 +35,8 @@ adaptive + ratingURL + http://82.202.236.70:9000 mainColor 27826 isAdaptive diff --git a/StepicAdaptiveCourse/LeaderboardNames/en.lproj/adjectives_f.plist b/StepicAdaptiveCourse/LeaderboardNames/en.lproj/adjectives_f.plist new file mode 100644 index 0000000000..52f8cc6406 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardNames/en.lproj/adjectives_f.plist @@ -0,0 +1,50 @@ + + + + + apricot + absolute + abstract + avant-garde + academic + active + alpine + astral + base + velvet + belgium + bermuda + complacent + prosperous + quick + thoughtful + plausible + fun + spring + strong-willed + well-mannered + galactic + proud + competent + business + active + precious + elysian + yellow + pearl + tan + wonderful + intricate + sharp + elect + elegant + refined + quantum + cool + crouching + malachite + honey + mighty + nuclear + + diff --git a/StepicAdaptiveCourse/LeaderboardNames/en.lproj/adjectives_m.plist b/StepicAdaptiveCourse/LeaderboardNames/en.lproj/adjectives_m.plist new file mode 100644 index 0000000000..52f8cc6406 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardNames/en.lproj/adjectives_m.plist @@ -0,0 +1,50 @@ + + + + + apricot + absolute + abstract + avant-garde + academic + active + alpine + astral + base + velvet + belgium + bermuda + complacent + prosperous + quick + thoughtful + plausible + fun + spring + strong-willed + well-mannered + galactic + proud + competent + business + active + precious + elysian + yellow + pearl + tan + wonderful + intricate + sharp + elect + elegant + refined + quantum + cool + crouching + malachite + honey + mighty + nuclear + + diff --git a/StepicAdaptiveCourse/LeaderboardNames/en.lproj/nouns_f.plist b/StepicAdaptiveCourse/LeaderboardNames/en.lproj/nouns_f.plist new file mode 100644 index 0000000000..d88cd89d6f --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardNames/en.lproj/nouns_f.plist @@ -0,0 +1,22 @@ + + + + + chinchilla + chupacabra + duck + frog + hyena + iguana + koala + llama + mink + otter + panda + pumpkin + shrew + squirrel + turtle + wolverine + + diff --git a/StepicAdaptiveCourse/LeaderboardNames/en.lproj/nouns_m.plist b/StepicAdaptiveCourse/LeaderboardNames/en.lproj/nouns_m.plist new file mode 100644 index 0000000000..0ffcbd0710 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardNames/en.lproj/nouns_m.plist @@ -0,0 +1,47 @@ + + + + + alligator + anteater + armadillo + auroch + axolotl + badger + beaver + buffalo + camel + chameleon + cheetah + chipmunk + coyote + crow + dingo + dinosaur + dolphin + elephant + ferret + fox + giraffe + gopher + grizzly + hedgehog + hippo + kraken + lemur + leopard + narwhal + cat + penguin + platypus + python + quagga + rabbit + raccoon + rhino + walrus + wolf + wombat + bear + + diff --git a/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/adjectives_f.plist b/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/adjectives_f.plist new file mode 100644 index 0000000000..63275dc054 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/adjectives_f.plist @@ -0,0 +1,50 @@ + + + + + абрикосовая + абсолютная + абстрактная + авангардная + академическая + активная + альпийская + астральная + базисная + бархатная + бельгийская + бермудская + благодушная + благополучная + быстрая + вдумчивая + вероятная + весёлая + весенняя + волевая + воспитанная + галактическая + гордая + грамотная + деловой + деятельная + драгоценная + елисейская + жёлтая + жемчужная + загорелая + замечательная + затейливая + зоркая + избранная + изящная + изысканная + квантовая + классная + крадущаяся + малахитовая + милая + могучая + ядерная + + diff --git a/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/adjectives_m.plist b/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/adjectives_m.plist new file mode 100644 index 0000000000..b57b3b4623 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/adjectives_m.plist @@ -0,0 +1,50 @@ + + + + + абрикосовый + абсолютный + абстрактный + авангардный + академический + активный + альпийский + астральный + базисный + бархатный + бельгийский + бермудский + благодушный + благополучный + быстрый + вдумчивый + вероятный + веселый + весенний + волевой + воспитанный + галактический + гордый + грамотный + деловой + деятельный + драгоценный + елисейский + жёлтый + жемчужный + загорелый + замечательный + затейливый + зоркий + избранный + изящный + изысканный + квантовый + классный + крадущийся + малахитовый + милый + могучий + ядерный + + diff --git a/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/nouns_f.plist b/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/nouns_f.plist new file mode 100644 index 0000000000..fe804cf994 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/nouns_f.plist @@ -0,0 +1,22 @@ + + + + + шиншилла + чупакабра + утка + лягушка + гиена + игуана + коала + лама + норка + выдра + панда + тыква + землеройка + белка + черепаха + росомаха + + diff --git a/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/nouns_m.plist b/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/nouns_m.plist new file mode 100644 index 0000000000..d05db89689 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardNames/ru.lproj/nouns_m.plist @@ -0,0 +1,47 @@ + + + + + крокодил + муравьед + броненосец + зубр + аксолотль + барсук + бобр + бизон + верблюд + хамелеон + гепард + бурундук + койот + ворон + динго + динозавр + дельфин + слон + хорек + лис + жираф + суслик + гризли + ёжик + бегемот + кракен + лемур + леопард + нарвал + кот + пингвин + утконос + питон + квагги + кролик + енот + носорог + морж + волк + вомбат + медведь + + diff --git a/StepicAdaptiveCourse/LeaderboardTableViewCell.swift b/StepicAdaptiveCourse/LeaderboardTableViewCell.swift new file mode 100644 index 0000000000..128b0866c8 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardTableViewCell.swift @@ -0,0 +1,157 @@ +// +// LeaderboardTableViewCell.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 15.08.2017. +// Copyright © 2017 Alex Karpov. All rights reserved. +// + +import UIKit + +class LeaderboardTableViewCell: UITableViewCell { + enum CellPosition { + case top, middle, bottom, separator + } + + static let reuseId = "LeaderboardTableViewCell" + + @IBOutlet weak var cardPadView: UIView! + @IBOutlet weak var userLabel: UILabel! + @IBOutlet weak var expLabel: UILabel! + @IBOutlet weak var medalImageView: UIImageView! + @IBOutlet weak var positionLabel: UILabel! + @IBOutlet weak var separatorImageView: UIImageView! + @IBOutlet weak var shadowPadView: UIView! + + private let meColor = UIColor(hex: 0xFFDCA5) + + var cellPosition: CellPosition = .middle { + didSet { + separatorImageView.isHidden = cellPosition != .separator + userLabel.isHidden = cellPosition == .separator + expLabel.isHidden = cellPosition == .separator + medalImageView.isHidden = cellPosition == .separator + positionLabel.isHidden = cellPosition == .separator + cardPadView.isHidden = cellPosition == .separator + shadowPadView.isHidden = cellPosition == .separator + } + } + + override func awakeFromNib() { + super.awakeFromNib() + + backgroundColor = .clear + positionLabel.isHidden = true + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + + drawRoundCorners() + drawShadow() + } + + override func prepareForReuse() { + super.prepareForReuse() + + cardPadView.backgroundColor = .white + positionLabel.isHidden = true + cellPosition = .middle + shadowPadView.layer.shadowPath = nil + } + + func updateInfo(position: Int, username: String, exp: Int, isMe: Bool = false) { + updatePosition(position) + userLabel.text = "\(username)" + expLabel.text = "\(exp)" + + if isMe { + cardPadView.backgroundColor = meColor + userLabel.text = NSLocalizedString("AdaptiveRatingYou", comment: "") + } + } + + fileprivate func updatePosition(_ position: Int) { + medalImageView.isHidden = false + positionLabel.isHidden = true + switch position { + case 1: + medalImageView.image = #imageLiteral(resourceName: "medal1") + break + case 2: + medalImageView.image = #imageLiteral(resourceName: "medal2") + break + case 3: + medalImageView.image = #imageLiteral(resourceName: "medal3") + break + default: + positionLabel.text = "\(position)." + positionLabel.isHidden = false + medalImageView.isHidden = true + } + } + + fileprivate func drawShadow() { + shadowPadView.backgroundColor = .clear + + let height = shadowPadView.frame.size.height + let width = shadowPadView.frame.size.width + let path = UIBezierPath() + switch cellPosition { + case .top: + path.move(to: CGPoint.zero) + path.addArc(withCenter: CGPoint(x: 10, y: 10), radius: 10, startAngle: -.pi, endAngle: -.pi / 2, clockwise: true) + path.addLine(to: CGPoint(x: width - 10, y: 0)) + path.addArc(withCenter: CGPoint(x: width - 10, y: 10), radius: 10, startAngle: -.pi / 2, endAngle: 0, clockwise: true) + path.addLine(to: CGPoint(x: width, y: height - 2.0)) + path.addLine(to: shadowPadView.center) + path.addLine(to: CGPoint(x: 0, y: height - 2.0)) + path.close() + shadowPadView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) + case .middle: + path.move(to: CGPoint.zero) + path.addLine(to: shadowPadView.center) + path.addLine(to: CGPoint(x: width, y: 0)) + path.addLine(to: CGPoint(x: width, y: height)) + path.addLine(to: shadowPadView.center) + path.addLine(to: CGPoint(x: 0, y: height)) + path.close() + shadowPadView.layer.shadowOffset = CGSize(width: 0.0, height: -2.0) + case .bottom: + path.move(to: CGPoint.zero) + path.addLine(to: shadowPadView.center) + path.addLine(to: CGPoint(x: width, y: 0)) + path.addLine(to: CGPoint(x: width, y: height)) + path.addArc(withCenter: CGPoint(x: width - 10, y: height - 10), radius: 10, startAngle: 0, endAngle: .pi / 2, clockwise: true) + path.addLine(to: CGPoint(x: 0, y: height)) + path.addArc(withCenter: CGPoint(x: 10, y: height - 10), radius: 10, startAngle: .pi / 2, endAngle: .pi, clockwise: true) + path.close() + shadowPadView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) + default: break + } + shadowPadView.layer.shadowPath = path.cgPath + + shadowPadView.layer.shadowOpacity = 0.2 + shadowPadView.layer.shadowRadius = 2.0 + shadowPadView.layer.shouldRasterize = true + shadowPadView.layer.rasterizationScale = UIScreen.main.scale + } + + fileprivate func drawRoundCorners() { + let maskLayer = CAShapeLayer() + if cellPosition == .top || cellPosition == .bottom { + let path = UIBezierPath(roundedRect: cardPadView.bounds, byRoundingCorners: cellPosition == .top ? [.topRight, .topLeft] : [.bottomRight, .bottomLeft], cornerRadii: CGSize(width: 10, height: 10)) + maskLayer.path = path.cgPath + } else { + maskLayer.path = UIBezierPath(rect: cardPadView.bounds).cgPath + } + cardPadView.layer.mask = maskLayer + } + + override func layoutSubviews() { + super.layoutSubviews() + + drawRoundCorners() + drawShadow() + } +} diff --git a/StepicAdaptiveCourse/LeaderboardTableViewCell.xib b/StepicAdaptiveCourse/LeaderboardTableViewCell.xib new file mode 100644 index 0000000000..b6927f69d7 --- /dev/null +++ b/StepicAdaptiveCourse/LeaderboardTableViewCell.xib @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StepicAdaptiveCourse/RatingsAPI.swift b/StepicAdaptiveCourse/RatingsAPI.swift new file mode 100644 index 0000000000..a993f1bd52 --- /dev/null +++ b/StepicAdaptiveCourse/RatingsAPI.swift @@ -0,0 +1,101 @@ +// +// RatingsAPI.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 15.08.2017. +// Copyright © 2017 Alex Karpov. All rights reserved. +// + +import Foundation +import Alamofire +import SwiftyJSON + +extension ApiDataDownloader { + static let adaptiveRatings = RatingsAPI() +} + +class RatingsAPI { + let name = "rating" + + typealias RatingRecord = (userId: Int, exp: Int, rank: Int) + typealias Scoreboard = (allCount: Int, leaders: [RatingRecord]) + + enum UpdateRatingResponse { + case ok, badRequest, serverError, connectionError(error: String) + } + + @discardableResult func update(courseId: Int, exp: Int, success: @escaping (UpdateRatingResponse) -> Void, error errorHandler: @escaping (UpdateRatingResponse) -> Void) -> Request? { + var params: Parameters = [ + "course": courseId, + "exp": exp + ] + if let token = AuthInfo.shared.token?.accessToken { + params["token"] = token + } + + return Alamofire.request("\(StepicApplicationsInfo.adaptiveRatingURL)/\(name)", method: .put, parameters: params, encoding: JSONEncoding.default, headers: nil).responseSwiftyJSON({ response in + var error = response.result.error + if response.result.value == nil { + if error == nil { + error = NSError(domain: "", code: -1, userInfo: nil) + } + } + let response = response.response + + if let e = error as NSError? { + errorHandler(.connectionError(error: "PUT adaptive rating: error \(e.domain) \(e.code): \(e.localizedDescription)")) + return + } + + switch response?.statusCode ?? 500 { + case 200: success(.ok) + case 401: errorHandler(.badRequest) + default: errorHandler(.serverError) + } + }) + } + + @discardableResult func retrieve(courseId: Int, count: Int = 10, days: Int? = 7, success: @escaping (Scoreboard) -> Void, error errorHandler: @escaping (String) -> Void) -> Request? { + + var params: Parameters = [ + "course": courseId, + "count": count + ] + if let days = days { + params["days"] = days + } + if let userId = AuthInfo.shared.userId { + params["user"] = userId + } + + return Alamofire.request("\(StepicApplicationsInfo.adaptiveRatingURL)/\(name)", method: .get, parameters: params, encoding: URLEncoding.default, headers: nil).responseSwiftyJSON({ response in + + var error = response.result.error + var json: JSON = [:] + if response.result.value == nil { + if error == nil { + error = NSError() + } + } else { + json = response.result.value! + } + let response = response.response + + if let e = error { + let d = (e as NSError).localizedDescription + print(d) + errorHandler(d) + return + } + + if response?.statusCode == 200 { + let leaders = json["users"].arrayValue.map({return RatingRecord(userId: $0["user"].intValue, exp: $0["exp"].intValue, rank: $0["rank"].intValue)}) + success(Scoreboard(allCount: json["count"].intValue, leaders: leaders)) + return + } else { + errorHandler("Response status code is wrong(\(String(describing: response?.statusCode)))") + return + } + }) + } +} diff --git a/StepicAdaptiveCourse/StringHelper.swift b/StepicAdaptiveCourse/StringHelper.swift index 57899c3f7a..6e32b35c9a 100644 --- a/StepicAdaptiveCourse/StringHelper.swift +++ b/StepicAdaptiveCourse/StringHelper.swift @@ -23,4 +23,9 @@ class StringHelper { return randomString } + + static func pluralize(number: Int, forms: [String]) -> String { + return number % 10 == 1 && number % 100 != 11 ? forms[0] : + (number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20) ? forms[1] : forms[2]) + } } diff --git a/StepicAdaptiveCourse/en.lproj/AdaptiveMain.strings b/StepicAdaptiveCourse/en.lproj/AdaptiveMain.strings index 75f18a17d5..8a699a20ce 100644 --- a/StepicAdaptiveCourse/en.lproj/AdaptiveMain.strings +++ b/StepicAdaptiveCourse/en.lproj/AdaptiveMain.strings @@ -5,6 +5,9 @@ /* Class = "UISegmentedControl"; 8WM-do-txJ.segmentTitles[1] = "Достижения"; ObjectID = "8WM-do-txJ"; */ "8WM-do-txJ.segmentTitles[1]" = "Achievements"; +/* Class = "UISegmentedControl"; 8WM-do-txJ.segmentTitles[2] = "Рейтинг"; ObjectID = "8WM-do-txJ"; */ +"8WM-do-txJ.segmentTitles[2]" = "Rating"; + /* Class = "UILabel"; text = "13 подряд"; ObjectID = "PXK-7E-juQ"; */ "PXK-7E-juQ.text" = ""; @@ -28,3 +31,9 @@ /* Class = "UINavigationItem"; text = "Статистика"; ObjectID = "y86-yn-TzS"; */ "y86-yn-TzS.title" = "Stats"; + +/* Class = "UISegmentedControl"; KpV-Qn-80f.segmentTitles[0] = "Всё время"; ObjectID = "KpV-Qn-80f"; */ +"KpV-Qn-80f.segmentTitles[0]" = "All time"; + +/* Class = "UISegmentedControl"; KpV-Qn-80f.segmentTitles[1] = "Последние 7 дней"; ObjectID = "KpV-Qn-80f"; */ +"KpV-Qn-80f.segmentTitles[1]" = "Last 7 days"; diff --git a/StepicAdaptiveCourse/ru.lproj/AdaptiveMain.strings b/StepicAdaptiveCourse/ru.lproj/AdaptiveMain.strings index 723ab9f454..95c10f2cd9 100644 --- a/StepicAdaptiveCourse/ru.lproj/AdaptiveMain.strings +++ b/StepicAdaptiveCourse/ru.lproj/AdaptiveMain.strings @@ -5,6 +5,9 @@ /* Class = "UISegmentedControl"; 8WM-do-txJ.segmentTitles[1] = "Достижения"; ObjectID = "8WM-do-txJ"; */ "8WM-do-txJ.segmentTitles[1]" = "Достижения"; +/* Class = "UISegmentedControl"; 8WM-do-txJ.segmentTitles[2] = "Рейтинг"; ObjectID = "8WM-do-txJ"; */ +"8WM-do-txJ.segmentTitles[2]" = "Рейтинг"; + /* Class = "UILabel"; text = "13 подряд"; ObjectID = "PXK-7E-juQ"; */ "PXK-7E-juQ.text" = ""; @@ -28,3 +31,9 @@ /* Class = "UINavigationItem"; text = "Статистика"; ObjectID = "y86-yn-TzS"; */ "y86-yn-TzS.title" = "Статистика"; + +/* Class = "UISegmentedControl"; KpV-Qn-80f.segmentTitles[0] = "Всё время"; ObjectID = "KpV-Qn-80f"; */ +"KpV-Qn-80f.segmentTitles[0]" = "Всё время"; + +/* Class = "UISegmentedControl"; KpV-Qn-80f.segmentTitles[1] = "Последние 7 дней"; ObjectID = "KpV-Qn-80f"; */ +"KpV-Qn-80f.segmentTitles[1]" = "Последние 7 дней";