diff --git a/Podfile b/Podfile index 7967fa3672..e06b6b4de4 100644 --- a/Podfile +++ b/Podfile @@ -24,7 +24,6 @@ def all_pods pod 'SVProgressHUD' pod 'FLKAutoLayout', '1.0.1' pod 'TSMessages', :git => 'https://github.com/KrauseFx/TSMessages.git' - pod 'DZNEmptyDataSet' pod 'YandexMobileMetrica/Dynamic' pod 'FirebaseCore' diff --git a/Stepic.xcodeproj/project.pbxproj b/Stepic.xcodeproj/project.pbxproj index b00287aba4..9e6c8741cb 100644 --- a/Stepic.xcodeproj/project.pbxproj +++ b/Stepic.xcodeproj/project.pbxproj @@ -761,13 +761,6 @@ 0897009B1F6B2A830041C24E /* NibInitializableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089700951F6B2A820041C24E /* NibInitializableView.swift */; }; 0897009C1F6B2A830041C24E /* NibInitializableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089700951F6B2A820041C24E /* NibInitializableView.swift */; }; 0897009D1F6B2A830041C24E /* NibInitializableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089700951F6B2A820041C24E /* NibInitializableView.swift */; }; - 08970ECF1C6A326900846119 /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECA1C6A326900846119 /* PlaceholderView.swift */; }; - 08970ED01C6A326900846119 /* PlaceholderViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */; }; - 08970ED11C6A326900846119 /* PlaceholderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */; }; - 08970ED21C6A326900846119 /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECD1C6A326900846119 /* Styles.swift */; }; - 08970ED31C6A326900846119 /* PlaceholderStyleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */; }; - 08970ED71C6A36E500846119 /* PlaceholderTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */; }; - 08970ED81C6A36E500846119 /* PlaceholderView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */; }; 089984291ECDE188005C0B27 /* LessonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089984281ECDE188005C0B27 /* LessonViewController.swift */; }; 0899842A1ECDE188005C0B27 /* LessonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089984281ECDE188005C0B27 /* LessonViewController.swift */; }; 0899842C1ECDE194005C0B27 /* LessonPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0899842B1ECDE194005C0B27 /* LessonPresenter.swift */; }; @@ -1103,9 +1096,7 @@ 08D1207A1C937B2200A54ABC /* Dataset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F5554F1C4F93B700C877E8 /* Dataset.swift */; }; 08D1207B1C937B2200A54ABC /* WebStepViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0891424F1BCFFB7F0000BCB0 /* WebStepViewController.swift */; }; 08D1207D1C937B2200A54ABC /* LabelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CA59E91BBD3D55008DC44D /* LabelExtension.swift */; }; - 08D1207E1C937B2200A54ABC /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECA1C6A326900846119 /* PlaceholderView.swift */; }; 08D1207F1C937B2200A54ABC /* Progress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083AABE71BE8D63C005E1E96 /* Progress.swift */; }; - 08D120801C937B2200A54ABC /* PlaceholderStyleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */; }; 08D120811C937B2200A54ABC /* MathQuizViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F485AB1C580DB3000165AA /* MathQuizViewController.swift */; }; 08D120821C937B2200A54ABC /* VideoURL+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089142461BCEE4EE0000BCB0 /* VideoURL+CoreDataProperties.swift */; }; 08D120831C937B2200A54ABC /* Video+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089142441BCEE4EE0000BCB0 /* Video+CoreDataProperties.swift */; }; @@ -1130,12 +1121,10 @@ 08D120981C937B2200A54ABC /* PathManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DF1D8B1BDA77A200BA35EA /* PathManager.swift */; }; 08D120991C937B2200A54ABC /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DF1D911BDAB93900BA35EA /* StringExtensions.swift */; }; 08D1209A1C937B2200A54ABC /* Attempt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F5554D1C4F924000C877E8 /* Attempt.swift */; }; - 08D1209B1C937B2200A54ABC /* PlaceholderViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */; }; 08D1209C1C937B2200A54ABC /* ControllerHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083D64AE1C19BDB2003222F0 /* ControllerHelper.swift */; }; 08D1209D1C937B2200A54ABC /* Progress+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083AABE61BE8D63C005E1E96 /* Progress+CoreDataProperties.swift */; }; 08D1209E1C937B2200A54ABC /* ButtonExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CA59ED1BBFC962008DC44D /* ButtonExtension.swift */; }; 08D120A01C937B2200A54ABC /* TeachersTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CA59E51BBD25DC008DC44D /* TeachersTableViewCell.swift */; }; - 08D120A11C937B2200A54ABC /* PlaceholderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */; }; 08D120A21C937B2200A54ABC /* SortingQuizTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F485B61C58ECE3000165AA /* SortingQuizTableViewCell.swift */; }; 08D120A31C937B2200A54ABC /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A0DA61BE9FFCE004AF4EB /* UIViewExtensions.swift */; }; 08D120A41C937B2200A54ABC /* Section+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0828FF6C1BC5D177000AFEA7 /* Section+CoreDataProperties.swift */; }; @@ -1172,7 +1161,6 @@ 08D120C91C937B2200A54ABC /* ChoiceReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F5555B1C4FB1E300C877E8 /* ChoiceReply.swift */; }; 08D120CA1C937B2200A54ABC /* VideoQualityTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F74651BD924B80064AAEA /* VideoQualityTableViewController.swift */; }; 08D120CB1C937B2200A54ABC /* MathReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F485A91C580D61000165AA /* MathReply.swift */; }; - 08D120CC1C937B2200A54ABC /* PlaceholderTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */; }; 08D120CD1C937B2200A54ABC /* StepicVideoPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08EF9A051C91D0F800433E4A /* StepicVideoPlayerViewController.swift */; }; 08D120CE1C937B2200A54ABC /* StringDatasetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F555551C4F9FAB00C877E8 /* StringDatasetExtension.swift */; }; 08D120CF1C937B2200A54ABC /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CA59F01BBFD65E008DC44D /* User.swift */; }; @@ -1206,7 +1194,6 @@ 08D120F01C937B2200A54ABC /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DE94381B8E3FCE00D278AB /* UIColorExtensions.swift */; }; 08D120F11C937B2200A54ABC /* Meta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0885F8531BA9DB5C00F2A188 /* Meta.swift */; }; 08D120F31C937B2200A54ABC /* StepicToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080F31DC1BA7162C00F356A0 /* StepicToken.swift */; }; - 08D120F41C937B2200A54ABC /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECD1C6A326900846119 /* Styles.swift */; }; 08D120F51C937B2200A54ABC /* SectionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0828FF781BC5E744000AFEA7 /* SectionsViewController.swift */; }; 08D120F61C937B2200A54ABC /* Sorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08A1256E1BDE8E460066B2B2 /* Sorter.swift */; }; 08D120F81C937B2200A54ABC /* ChoiceDataset.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F555531C4F97C100C877E8 /* ChoiceDataset.swift */; }; @@ -1232,7 +1219,6 @@ 08D121161C937B2200A54ABC /* Scripts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 08DF1D951BDADB7A00BA35EA /* Scripts.plist */; }; 08D121171C937B2200A54ABC /* VideoDownloadView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 086E965D1BF6683800AB952D /* VideoDownloadView.xib */; }; 08D121181C937B2200A54ABC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 08A1257B1BDEBCC90066B2B2 /* Localizable.strings */; }; - 08D121191C937B2200A54ABC /* PlaceholderView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */; }; 08D1211A1C937B2200A54ABC /* WarningView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08F485941C574DAD000165AA /* WarningView.xib */; }; 08D1211B1C937B2200A54ABC /* TeacherCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08CA59DB1BBC00B9008DC44D /* TeacherCollectionViewCell.xib */; }; 08D1211C1C937B2200A54ABC /* SectionTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0828FF751BC5E6B0000AFEA7 /* SectionTableViewCell.xib */; }; @@ -1620,13 +1606,7 @@ 2C1B60EB1F4C4AEF00236804 /* RateAppAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082799471E9B81AF008A3786 /* RateAppAlertManager.swift */; }; 2C1B60EC1F4C4AEF00236804 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 08E16F221BDBA4AF004822E1 /* Reachability.m */; }; 2C1B60ED1F4C4AEF00236804 /* ControllerHelperLaunchExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DA79001DB6BB36003491C4 /* ControllerHelperLaunchExtension.swift */; }; - 2C1B60EE1F4C4AEF00236804 /* PlaceholderTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */; }; - 2C1B60EF1F4C4AEF00236804 /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECA1C6A326900846119 /* PlaceholderView.swift */; }; 2C1B60F01F4C4AEF00236804 /* CodeSnippetSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9111F10C5480087D61B /* CodeSnippetSymbols.swift */; }; - 2C1B60F11F4C4AEF00236804 /* PlaceholderViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */; }; - 2C1B60F21F4C4AEF00236804 /* PlaceholderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */; }; - 2C1B60F31F4C4AEF00236804 /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECD1C6A326900846119 /* Styles.swift */; }; - 2C1B60F41F4C4AEF00236804 /* PlaceholderStyleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */; }; 2C1B60F51F4C4AEF00236804 /* VideoDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086E965F1BF66A1800AB952D /* VideoDownloadView.swift */; }; 2C1B60F61F4C4AEF00236804 /* CodeSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080C5E7A1EFC13ED0036EB3D /* CodeSample.swift */; }; 2C1B60F71F4C4AEF00236804 /* AdaptiveRatingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C76ACCC1F16496C0077D9D7 /* AdaptiveRatingManager.swift */; }; @@ -1933,7 +1913,6 @@ 2C1B625F1F4C4AEF00236804 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941A1B8C58AC00D278AB /* Main.storyboard */; }; 2C1B62601F4C4AEF00236804 /* StepCardView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 860DB7911EB8DC13001E3E42 /* StepCardView.xib */; }; 2C1B62611F4C4AEF00236804 /* CodeSuggestionsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0860D9181F115D830087D61B /* CodeSuggestionsTableViewController.xib */; }; - 2C1B62621F4C4AEF00236804 /* PlaceholderView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */; }; 2C1B62631F4C4AEF00236804 /* VideoDownloadView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 086E965D1BF6683800AB952D /* VideoDownloadView.xib */; }; 2C1B62651F4C4AEF00236804 /* WarningView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08F485941C574DAD000165AA /* WarningView.xib */; }; 2C1B62671F4C4AEF00236804 /* StepTabView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 083622DB1CD1FA6700CD8915 /* StepTabView.xib */; }; @@ -2024,13 +2003,7 @@ 2C1B62FC1F4C590700236804 /* RateAppAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082799471E9B81AF008A3786 /* RateAppAlertManager.swift */; }; 2C1B62FD1F4C590700236804 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 08E16F221BDBA4AF004822E1 /* Reachability.m */; }; 2C1B62FE1F4C590700236804 /* ControllerHelperLaunchExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DA79001DB6BB36003491C4 /* ControllerHelperLaunchExtension.swift */; }; - 2C1B62FF1F4C590700236804 /* PlaceholderTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */; }; - 2C1B63001F4C590700236804 /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECA1C6A326900846119 /* PlaceholderView.swift */; }; 2C1B63011F4C590700236804 /* CodeSnippetSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9111F10C5480087D61B /* CodeSnippetSymbols.swift */; }; - 2C1B63021F4C590700236804 /* PlaceholderViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */; }; - 2C1B63031F4C590700236804 /* PlaceholderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */; }; - 2C1B63041F4C590700236804 /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECD1C6A326900846119 /* Styles.swift */; }; - 2C1B63051F4C590700236804 /* PlaceholderStyleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */; }; 2C1B63061F4C590700236804 /* VideoDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086E965F1BF66A1800AB952D /* VideoDownloadView.swift */; }; 2C1B63071F4C590700236804 /* CodeSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080C5E7A1EFC13ED0036EB3D /* CodeSample.swift */; }; 2C1B63081F4C590700236804 /* AdaptiveRatingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C76ACCC1F16496C0077D9D7 /* AdaptiveRatingManager.swift */; }; @@ -2337,7 +2310,6 @@ 2C1B64701F4C590700236804 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941A1B8C58AC00D278AB /* Main.storyboard */; }; 2C1B64711F4C590700236804 /* StepCardView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 860DB7911EB8DC13001E3E42 /* StepCardView.xib */; }; 2C1B64721F4C590700236804 /* CodeSuggestionsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0860D9181F115D830087D61B /* CodeSuggestionsTableViewController.xib */; }; - 2C1B64731F4C590700236804 /* PlaceholderView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */; }; 2C1B64741F4C590700236804 /* VideoDownloadView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 086E965D1BF6683800AB952D /* VideoDownloadView.xib */; }; 2C1B64761F4C590700236804 /* WarningView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08F485941C574DAD000165AA /* WarningView.xib */; }; 2C1B64781F4C590700236804 /* StepTabView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 083622DB1CD1FA6700CD8915 /* StepTabView.xib */; }; @@ -2773,13 +2745,7 @@ 2C89A97F1F4C289900227C3B /* RateAppAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082799471E9B81AF008A3786 /* RateAppAlertManager.swift */; }; 2C89A9801F4C289900227C3B /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 08E16F221BDBA4AF004822E1 /* Reachability.m */; }; 2C89A9811F4C289900227C3B /* ControllerHelperLaunchExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DA79001DB6BB36003491C4 /* ControllerHelperLaunchExtension.swift */; }; - 2C89A9821F4C289900227C3B /* PlaceholderTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */; }; - 2C89A9831F4C289900227C3B /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECA1C6A326900846119 /* PlaceholderView.swift */; }; 2C89A9841F4C289900227C3B /* CodeSnippetSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9111F10C5480087D61B /* CodeSnippetSymbols.swift */; }; - 2C89A9851F4C289900227C3B /* PlaceholderViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */; }; - 2C89A9861F4C289900227C3B /* PlaceholderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */; }; - 2C89A9871F4C289900227C3B /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECD1C6A326900846119 /* Styles.swift */; }; - 2C89A9881F4C289900227C3B /* PlaceholderStyleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */; }; 2C89A9891F4C289900227C3B /* VideoDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086E965F1BF66A1800AB952D /* VideoDownloadView.swift */; }; 2C89A98A1F4C289900227C3B /* CodeSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080C5E7A1EFC13ED0036EB3D /* CodeSample.swift */; }; 2C89A98B1F4C289900227C3B /* AdaptiveRatingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C76ACCC1F16496C0077D9D7 /* AdaptiveRatingManager.swift */; }; @@ -3085,7 +3051,6 @@ 2C89AAF01F4C289900227C3B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941A1B8C58AC00D278AB /* Main.storyboard */; }; 2C89AAF11F4C289900227C3B /* StepCardView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 860DB7911EB8DC13001E3E42 /* StepCardView.xib */; }; 2C89AAF21F4C289900227C3B /* CodeSuggestionsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0860D9181F115D830087D61B /* CodeSuggestionsTableViewController.xib */; }; - 2C89AAF31F4C289900227C3B /* PlaceholderView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */; }; 2C89AAF41F4C289900227C3B /* VideoDownloadView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 086E965D1BF6683800AB952D /* VideoDownloadView.xib */; }; 2C89AAF61F4C289900227C3B /* WarningView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08F485941C574DAD000165AA /* WarningView.xib */; }; 2C89AAF71F4C289900227C3B /* MathJax in Resources */ = {isa = PBXBuildFile; fileRef = 08E542001C6A76E100DEC38E /* MathJax */; }; @@ -3182,13 +3147,7 @@ 2C9731331F4C38F600AC9301 /* RateAppAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082799471E9B81AF008A3786 /* RateAppAlertManager.swift */; }; 2C9731341F4C38F600AC9301 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 08E16F221BDBA4AF004822E1 /* Reachability.m */; }; 2C9731351F4C38F600AC9301 /* ControllerHelperLaunchExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08DA79001DB6BB36003491C4 /* ControllerHelperLaunchExtension.swift */; }; - 2C9731361F4C38F600AC9301 /* PlaceholderTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */; }; - 2C9731371F4C38F600AC9301 /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECA1C6A326900846119 /* PlaceholderView.swift */; }; 2C9731381F4C38F600AC9301 /* CodeSnippetSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0860D9111F10C5480087D61B /* CodeSnippetSymbols.swift */; }; - 2C9731391F4C38F600AC9301 /* PlaceholderViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */; }; - 2C97313A1F4C38F600AC9301 /* PlaceholderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */; }; - 2C97313B1F4C38F600AC9301 /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECD1C6A326900846119 /* Styles.swift */; }; - 2C97313C1F4C38F600AC9301 /* PlaceholderStyleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */; }; 2C97313D1F4C38F600AC9301 /* VideoDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086E965F1BF66A1800AB952D /* VideoDownloadView.swift */; }; 2C97313E1F4C38F600AC9301 /* CodeSample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080C5E7A1EFC13ED0036EB3D /* CodeSample.swift */; }; 2C97313F1F4C38F600AC9301 /* AdaptiveRatingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C76ACCC1F16496C0077D9D7 /* AdaptiveRatingManager.swift */; }; @@ -3495,7 +3454,6 @@ 2C9732A41F4C38F600AC9301 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941A1B8C58AC00D278AB /* Main.storyboard */; }; 2C9732A51F4C38F600AC9301 /* StepCardView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 860DB7911EB8DC13001E3E42 /* StepCardView.xib */; }; 2C9732A61F4C38F600AC9301 /* CodeSuggestionsTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0860D9181F115D830087D61B /* CodeSuggestionsTableViewController.xib */; }; - 2C9732A71F4C38F600AC9301 /* PlaceholderView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */; }; 2C9732A81F4C38F600AC9301 /* VideoDownloadView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 086E965D1BF6683800AB952D /* VideoDownloadView.xib */; }; 2C9732AA1F4C38F600AC9301 /* WarningView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08F485941C574DAD000165AA /* WarningView.xib */; }; 2C9732AC1F4C38F600AC9301 /* StepTabView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 083622DB1CD1FA6700CD8915 /* StepTabView.xib */; }; @@ -3895,12 +3853,6 @@ 864D673E1E83DE8A001E8D9E /* TCBlobDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 080ACF5F1BD7DE2A00329F2B /* TCBlobDownloadManager.swift */; }; 864D673F1E83DE8A001E8D9E /* Alamofire-SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0824289B1BB0104700C98185 /* Alamofire-SwiftyJSON.swift */; }; 864D67401E83DE8A001E8D9E /* JSQWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 083B164C1C2AF27700250B37 /* JSQWebViewController.swift */; }; - 864D67411E83E08E001E8D9E /* PlaceholderTestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */; }; - 864D67431E83E08E001E8D9E /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECA1C6A326900846119 /* PlaceholderView.swift */; }; - 864D67441E83E08E001E8D9E /* PlaceholderViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */; }; - 864D67451E83E08E001E8D9E /* PlaceholderViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */; }; - 864D67461E83E08E001E8D9E /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECD1C6A326900846119 /* Styles.swift */; }; - 864D67471E83E08E001E8D9E /* PlaceholderStyleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */; }; 864D67481E83E08E001E8D9E /* VideoDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086E965F1BF66A1800AB952D /* VideoDownloadView.swift */; }; 864D674A1E83E08E001E8D9E /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F485921C574D79000165AA /* WarningView.swift */; }; 864D674C1E83E08E001E8D9E /* UIViewLoadExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08F485971C5752D0000165AA /* UIViewLoadExtension.swift */; }; @@ -3979,12 +3931,28 @@ 864D67D61E83E394001E8D9E /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 08E16F221BDBA4AF004822E1 /* Reachability.m */; }; 86624A731FC76578008E7E6C /* NotificationStatusesAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86624A721FC76578008E7E6C /* NotificationStatusesAPI.swift */; }; 86624A751FC76682008E7E6C /* NotificationsStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86624A741FC76682008E7E6C /* NotificationsStatus.swift */; }; + 866AD0D4206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D3206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift */; }; + 866AD0D62061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D52061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift */; }; 86795EF11E84AA1400A985C2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08DE941A1B8C58AC00D278AB /* Main.storyboard */; }; 86795EF71E85325000A985C2 /* RecommendationsAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86795EF61E85325000A985C2 /* RecommendationsAPI.swift */; }; 868BC6E01EA0263300E204EE /* StreaksView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08F4761D1E82C11C0018E82C /* StreaksView.xib */; }; 868BC6E11EA0263300E204EE /* RateAppViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 089056101E98021000B8FE6A /* RateAppViewController.xib */; }; 869927D61ECFA9D9007D65A5 /* ShareableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 082348381ECA264000D2CB40 /* ShareableController.swift */; }; - 86AE946E1E84511A00F67691 /* PlaceholderView.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */; }; + 86A1C3262065157C00D0914C /* StepikPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CF0885C205BED9700FCB9C0 /* StepikPlaceholderView.xib */; }; + 86A1C3272065157D00D0914C /* StepikPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CF0885C205BED9700FCB9C0 /* StepikPlaceholderView.xib */; }; + 86A1C3292065157E00D0914C /* StepikPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CF0885C205BED9700FCB9C0 /* StepikPlaceholderView.xib */; }; + 86A1C32A2065157F00D0914C /* StepikPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CF0885C205BED9700FCB9C0 /* StepikPlaceholderView.xib */; }; + 86A1C32B2065158000D0914C /* StepikPlaceholderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2CF0885C205BED9700FCB9C0 /* StepikPlaceholderView.xib */; }; + 86A1C331206515B200D0914C /* ControllerWithStepikPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D52061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift */; }; + 86A1C332206515B200D0914C /* ControllerWithStepikPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D52061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift */; }; + 86A1C333206515B300D0914C /* ControllerWithStepikPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D52061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift */; }; + 86A1C334206515B300D0914C /* ControllerWithStepikPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D52061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift */; }; + 86A1C335206515B400D0914C /* ControllerWithStepikPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D52061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift */; }; + 86A1C3362065173700D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D3206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift */; }; + 86A1C3372065173700D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D3206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift */; }; + 86A1C3382065173800D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D3206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift */; }; + 86A1C3392065173800D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D3206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift */; }; + 86A1C33A2065173900D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866AD0D3206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift */; }; 86AE946F1E84511A00F67691 /* VideoDownloadView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 086E965D1BF6683800AB952D /* VideoDownloadView.xib */; }; 86AE94701E84511A00F67691 /* WarningView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08F485941C574DAD000165AA /* WarningView.xib */; }; 86AE94711E84511A00F67691 /* StepTabView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 083622DB1CD1FA6700CD8915 /* StepTabView.xib */; }; @@ -4688,13 +4656,6 @@ 089611031D52250500561AC1 /* DeepLinkRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DeepLinkRouter.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 08964BCC1F3072BA00DBBCCE /* QueriesAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueriesAPI.swift; sourceTree = ""; }; 089700951F6B2A820041C24E /* NibInitializableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibInitializableView.swift; sourceTree = ""; }; - 08970ECA1C6A326900846119 /* PlaceholderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderView.swift; sourceTree = ""; }; - 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderViewDataSource.swift; sourceTree = ""; }; - 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderViewDelegate.swift; sourceTree = ""; }; - 08970ECD1C6A326900846119 /* Styles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Styles.swift; sourceTree = ""; }; - 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderStyleExtensions.swift; sourceTree = ""; }; - 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceholderTestViewController.swift; sourceTree = ""; }; - 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = PlaceholderView.storyboard; sourceTree = ""; }; 089984281ECDE188005C0B27 /* LessonViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LessonViewController.swift; sourceTree = ""; }; 0899842B1ECDE194005C0B27 /* LessonPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LessonPresenter.swift; sourceTree = ""; }; 0899842E1ECDE19E005C0B27 /* LessonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LessonView.swift; sourceTree = ""; }; @@ -5178,6 +5139,8 @@ 863262F81ECFC5AE007A20B3 /* loading_robot.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = loading_robot.gif; sourceTree = ""; }; 86624A721FC76578008E7E6C /* NotificationStatusesAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationStatusesAPI.swift; sourceTree = ""; }; 86624A741FC76682008E7E6C /* NotificationsStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsStatus.swift; sourceTree = ""; }; + 866AD0D3206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StepikPlaceholderStyle+Placeholders.swift"; sourceTree = ""; }; + 866AD0D52061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerWithStepikPlaceholder.swift; sourceTree = ""; }; 86795EF61E85325000A985C2 /* RecommendationsAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RecommendationsAPI.swift; path = Stepic/RecommendationsAPI.swift; sourceTree = SOURCE_ROOT; }; 86ABC66B1E96867A0012E8A6 /* AdaptiveStepCardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdaptiveStepCardView.swift; sourceTree = ""; }; 86BB7C012019538000063538 /* CongratsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CongratsView.swift; sourceTree = ""; }; @@ -6263,7 +6226,6 @@ 2C453396204D46B70061342A /* PinsMap */, 2C9704221F3B1BFE00C36F0A /* TapProxyView.swift */, 08F476201E82C12B0018E82C /* StreaksView */, - 08970EC91C6A325600846119 /* PlaceholderView */, 08F485911C574CFC000165AA /* VideoDownloadView */, 08F485961C574DB3000165AA /* WarningView */, 08F485971C5752D0000165AA /* UIViewLoadExtension.swift */, @@ -6472,28 +6434,6 @@ name = Step; sourceTree = ""; }; - 08970EC91C6A325600846119 /* PlaceholderView */ = { - isa = PBXGroup; - children = ( - 08970ED41C6A369200846119 /* PlaceholderViewTesting */, - 08970ECA1C6A326900846119 /* PlaceholderView.swift */, - 08970ECB1C6A326900846119 /* PlaceholderViewDataSource.swift */, - 08970ECC1C6A326900846119 /* PlaceholderViewDelegate.swift */, - 08970ECD1C6A326900846119 /* Styles.swift */, - 08970ECE1C6A326900846119 /* PlaceholderStyleExtensions.swift */, - ); - name = PlaceholderView; - sourceTree = ""; - }; - 08970ED41C6A369200846119 /* PlaceholderViewTesting */ = { - isa = PBXGroup; - children = ( - 08970ED51C6A36E500846119 /* PlaceholderTestViewController.swift */, - 08970ED61C6A36E500846119 /* PlaceholderView.storyboard */, - ); - name = PlaceholderViewTesting; - sourceTree = ""; - }; 089F58871D22BD44000CD540 /* DiscussionCountView */ = { isa = PBXGroup; children = ( @@ -6878,6 +6818,7 @@ 08CA59D41BBA1628008DC44D /* Stepic-Bridging-Header.h */, 08DA79001DB6BB36003491C4 /* ControllerHelperLaunchExtension.swift */, 080F211A2034DA2500A1204C /* LocalProgressLastViewedUpdater.swift */, + 866AD0D52061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift */, ); path = Stepic; sourceTree = ""; @@ -8044,6 +7985,7 @@ children = ( 2CF08860205BEE2B00FCB9C0 /* StepikPlaceholder.swift */, 2CF08862205BEEB800FCB9C0 /* StepikPlaceholderView */, + 866AD0D3206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift */, ); name = Placeholders; sourceTree = ""; @@ -8991,7 +8933,6 @@ 08ED08E31FCCDC3A0053FB68 /* UserActivityHomeView.xib in Resources */, 08DF78A91F5DE66200AEEA85 /* ArtView.xib in Resources */, 08EB85E21D0F192A00E4F345 /* LoadMoreTableViewCell.xib in Resources */, - 08D121191C937B2200A54ABC /* PlaceholderView.storyboard in Resources */, 083622DD1CD1FA6700CD8915 /* StepTabView.xib in Resources */, 08CBA31E1F57562A00302154 /* SwitchMenuBlockTableViewCell.xib in Resources */, 08FEFC1F1F117257005CA0FB /* CodeSuggestionTableViewCell.xib in Resources */, @@ -9098,7 +9039,6 @@ 08CB0D3A1FB5FB28001A1E02 /* Explore.storyboard in Resources */, 2CC351861F6827BE004255B6 /* Auth.storyboard in Resources */, 083622DC1CD1FA6700CD8915 /* StepTabView.xib in Resources */, - 08970ED81C6A36E500846119 /* PlaceholderView.storyboard in Resources */, 08F485951C574DAD000165AA /* WarningView.xib in Resources */, 089056131E98021000B8FE6A /* RateAppViewController.xib in Resources */, 0869F6D81CE3684000F8A6DB /* default_sound.wav in Resources */, @@ -9197,7 +9137,6 @@ 2C1B625F1F4C4AEF00236804 /* Main.storyboard in Resources */, 2C1B62601F4C4AEF00236804 /* StepCardView.xib in Resources */, 2C1B62611F4C4AEF00236804 /* CodeSuggestionsTableViewController.xib in Resources */, - 2C1B62621F4C4AEF00236804 /* PlaceholderView.storyboard in Resources */, 2C1B62631F4C4AEF00236804 /* VideoDownloadView.xib in Resources */, 2C1B62651F4C4AEF00236804 /* WarningView.xib in Resources */, 2C608B602045729C006870BB /* step2.html in Resources */, @@ -9246,6 +9185,7 @@ 08C7CB7A1FA7ACB9001D8241 /* CourseListEmptyPlaceholder.xib in Resources */, 08CBA35A1F5A2D9400302154 /* Profile.storyboard in Resources */, 2C1B62841F4C4AEF00236804 /* SearchSuggestionTableViewCell.xib in Resources */, + 86A1C32A2065157F00D0914C /* StepikPlaceholderView.xib in Resources */, 08CBA3231F57562A00302154 /* SwitchMenuBlockTableViewCell.xib in Resources */, 08EDD6261F7C607A005203E4 /* CourseWidgetTableViewCell.xib in Resources */, 2C608AC6204568C5006870BB /* CongratulationViewController.xib in Resources */, @@ -9307,7 +9247,6 @@ 2C1B64701F4C590700236804 /* Main.storyboard in Resources */, 2C1B64711F4C590700236804 /* StepCardView.xib in Resources */, 2C1B64721F4C590700236804 /* CodeSuggestionsTableViewController.xib in Resources */, - 2C1B64731F4C590700236804 /* PlaceholderView.storyboard in Resources */, 2C1B64741F4C590700236804 /* VideoDownloadView.xib in Resources */, 2C1B64761F4C590700236804 /* WarningView.xib in Resources */, 2C608B5F2045729B006870BB /* step2.html in Resources */, @@ -9356,6 +9295,7 @@ 08C7CB7B1FA7ACB9001D8241 /* CourseListEmptyPlaceholder.xib in Resources */, 08CBA35B1F5A2D9400302154 /* Profile.storyboard in Resources */, 2C1B64961F4C590700236804 /* step4.html in Resources */, + 86A1C3292065157E00D0914C /* StepikPlaceholderView.xib in Resources */, 08CBA3241F57562A00302154 /* SwitchMenuBlockTableViewCell.xib in Resources */, 08EDD6271F7C607A005203E4 /* CourseWidgetTableViewCell.xib in Resources */, 2C608AC5204568C5006870BB /* CongratulationViewController.xib in Resources */, @@ -9425,7 +9365,6 @@ 2C89AAF11F4C289900227C3B /* StepCardView.xib in Resources */, 2C35C4E31F4DA487002F3BF4 /* nouns_f.plist in Resources */, 2C89AAF21F4C289900227C3B /* CodeSuggestionsTableViewController.xib in Resources */, - 2C89AAF31F4C289900227C3B /* PlaceholderView.storyboard in Resources */, 08CB0D481FB63F74001A1E02 /* ContentLanguagesView.xib in Resources */, 2C6A762B2045CE4E00C509A6 /* ProgressTableViewCell.xib in Resources */, 2C89AAF41F4C289900227C3B /* VideoDownloadView.xib in Resources */, @@ -9455,6 +9394,7 @@ 2C89AB0F1F4C289900227C3B /* LoadMoreTableViewCell.xib in Resources */, 2C6A76532045CF3B00C509A6 /* overlay_hard.png in Resources */, 2C89AB101F4C289900227C3B /* DiscussionsStoryboard.storyboard in Resources */, + 86A1C3272065157D00D0914C /* StepikPlaceholderView.xib in Resources */, 08C7CB781FA7ACB9001D8241 /* CourseListEmptyPlaceholder.xib in Resources */, 2C89AB111F4C289900227C3B /* QuizViewController.xib in Resources */, 2C89AB121F4C289900227C3B /* step4.html in Resources */, @@ -9529,7 +9469,6 @@ 2C9732A61F4C38F600AC9301 /* CodeSuggestionsTableViewController.xib in Resources */, 2C608B612045729D006870BB /* step2.html in Resources */, 2C35C4EA1F4DA48F002F3BF4 /* nouns_m.plist in Resources */, - 2C9732A71F4C38F600AC9301 /* PlaceholderView.storyboard in Resources */, 2C9732A81F4C38F600AC9301 /* VideoDownloadView.xib in Resources */, 2C9732AA1F4C38F600AC9301 /* WarningView.xib in Resources */, 2C4BBF1F203DC66F000A4250 /* plyr.css in Resources */, @@ -9562,6 +9501,7 @@ 08195A261FA0AF2A00E6D6CD /* HorizontalCoursesView.xib in Resources */, 2C9732C21F4C38F600AC9301 /* DiscussionsViewController.xib in Resources */, 087E8B27204DE2A100C35B66 /* NotificationRequestAlertViewController.xib in Resources */, + 86A1C32B2065158000D0914C /* StepikPlaceholderView.xib in Resources */, 2C9732C31F4C38F600AC9301 /* LoadMoreTableViewCell.xib in Resources */, 08CBA3461F57565700302154 /* TitleContentExpandableMenuBlockTableViewCell.xib in Resources */, 2C9732C41F4C38F600AC9301 /* DiscussionsStoryboard.storyboard in Resources */, @@ -9674,7 +9614,6 @@ 2C8652601F4B2F7D00D51654 /* Config.plist in Resources */, 86795EF11E84AA1400A985C2 /* Main.storyboard in Resources */, 085E9E711F138C3F00D6A4BC /* CodeSuggestionsTableViewController.xib in Resources */, - 86AE946E1E84511A00F67691 /* PlaceholderView.storyboard in Resources */, 86AE946F1E84511A00F67691 /* VideoDownloadView.xib in Resources */, 86AE94701E84511A00F67691 /* WarningView.xib in Resources */, 86AE94711E84511A00F67691 /* StepTabView.xib in Resources */, @@ -9711,6 +9650,7 @@ 2C4BBF17203DC66D000A4250 /* plyr.js in Resources */, 2C8652641F4B2F7D00D51654 /* GoogleService-Info.plist in Resources */, 08195A231FA0AF2A00E6D6CD /* HorizontalCoursesView.xib in Resources */, + 86A1C3262065157C00D0914C /* StepikPlaceholderView.xib in Resources */, 86AE947C1E84511A00F67691 /* LoadMoreTableViewCell.xib in Resources */, 86AE947D1E84511A00F67691 /* DiscussionsStoryboard.storyboard in Resources */, 08CBA3431F57565700302154 /* TitleContentExpandableMenuBlockTableViewCell.xib in Resources */, @@ -9861,7 +9801,6 @@ "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack-iOS/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", "${BUILT_PRODUCTS_DIR}/DeviceKit-iOS/DeviceKit.framework", "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework", "${BUILT_PRODUCTS_DIR}/EasyTipView/EasyTipView.framework", @@ -9906,7 +9845,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeviceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyTipView.framework", @@ -10089,7 +10027,6 @@ "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack-iOS/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", "${BUILT_PRODUCTS_DIR}/DeviceKit-iOS/DeviceKit.framework", "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework", "${BUILT_PRODUCTS_DIR}/EasyTipView/EasyTipView.framework", @@ -10134,7 +10071,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeviceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyTipView.framework", @@ -10346,7 +10282,6 @@ "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack-iOS/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", "${BUILT_PRODUCTS_DIR}/DeviceKit-iOS/DeviceKit.framework", "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework", "${BUILT_PRODUCTS_DIR}/EasyTipView/EasyTipView.framework", @@ -10387,7 +10322,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeviceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyTipView.framework", @@ -10520,7 +10454,6 @@ "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack-iOS/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", "${BUILT_PRODUCTS_DIR}/DeviceKit-iOS/DeviceKit.framework", "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework", "${BUILT_PRODUCTS_DIR}/EasyTipView/EasyTipView.framework", @@ -10565,7 +10498,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeviceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyTipView.framework", @@ -10932,7 +10864,6 @@ "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack-iOS/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", "${BUILT_PRODUCTS_DIR}/DeviceKit-iOS/DeviceKit.framework", "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework", "${BUILT_PRODUCTS_DIR}/EasyTipView/EasyTipView.framework", @@ -10977,7 +10908,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeviceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyTipView.framework", @@ -11050,7 +10980,6 @@ "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack-iOS/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", "${BUILT_PRODUCTS_DIR}/DeviceKit-iOS/DeviceKit.framework", "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework", "${BUILT_PRODUCTS_DIR}/EasyTipView/EasyTipView.framework", @@ -11095,7 +11024,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeviceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyTipView.framework", @@ -11201,7 +11129,6 @@ "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack-iOS/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", "${BUILT_PRODUCTS_DIR}/DeviceKit-iOS/DeviceKit.framework", "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework", "${BUILT_PRODUCTS_DIR}/EasyTipView/EasyTipView.framework", @@ -11242,7 +11169,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeviceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyTipView.framework", @@ -11366,7 +11292,6 @@ "${BUILT_PRODUCTS_DIR}/CRToast/CRToast.framework", "${BUILT_PRODUCTS_DIR}/Charts/Charts.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack-iOS/CocoaLumberjack.framework", - "${BUILT_PRODUCTS_DIR}/DZNEmptyDataSet/DZNEmptyDataSet.framework", "${BUILT_PRODUCTS_DIR}/DeviceKit-iOS/DeviceKit.framework", "${BUILT_PRODUCTS_DIR}/DownloadButton/DownloadButton.framework", "${BUILT_PRODUCTS_DIR}/EasyTipView/EasyTipView.framework", @@ -11409,7 +11334,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CRToast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DZNEmptyDataSet.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DeviceKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DownloadButton.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EasyTipView.framework", @@ -11528,12 +11452,10 @@ 08D1207D1C937B2200A54ABC /* LabelExtension.swift in Sources */, 08E8F9681F34DD2C008CF4A1 /* SearchQueriesPresenter.swift in Sources */, 0859AEA21E4F22B500A0D206 /* FillBlanksInputTableViewCell.swift in Sources */, - 08D1207E1C937B2200A54ABC /* PlaceholderView.swift in Sources */, 08D1207F1C937B2200A54ABC /* Progress.swift in Sources */, 0834C46A1E2CE469002F8516 /* MatchingDataset.swift in Sources */, 089574571E5B76E700C12D21 /* UIImageView+SVGDownload.swift in Sources */, 08D5F5781F7DA8CB007C1634 /* CourseReviewSummary+CoreDataProperties.swift in Sources */, - 08D120801C937B2200A54ABC /* PlaceholderStyleExtensions.swift in Sources */, 0864949B1E8C78A20083E0BE /* SbStepicApplicationsInfo.swift in Sources */, 08D120811C937B2200A54ABC /* MathQuizViewController.swift in Sources */, 086953101D747DC0003857A2 /* RequestChain.swift in Sources */, @@ -11617,7 +11539,6 @@ 08AF59F71E6D9BE800423EFF /* RGPageViewController+UIToolbarDelegate.swift in Sources */, 08E8F9751F34E3D5008CF4A1 /* SearchQueriesViewController.swift in Sources */, 088EDCA71F9496DE0098DEC7 /* CourseListHorizontalViewController.swift in Sources */, - 08D1209B1C937B2200A54ABC /* PlaceholderViewDataSource.swift in Sources */, 080CE1561E95635A0089A27F /* ProgressesAPI.swift in Sources */, 084F7AA81E76EF690088368A /* LastStep.swift in Sources */, 0860D9161F10EA690087D61B /* InputAccessoryBuilder.swift in Sources */, @@ -11635,7 +11556,6 @@ 08D120A01C937B2200A54ABC /* TeachersTableViewCell.swift in Sources */, 082BE3B31E67686B006BC60F /* RoutingManager.swift in Sources */, 080CE15C1E95804C0089A27F /* SearchResultsAPI.swift in Sources */, - 08D120A11C937B2200A54ABC /* PlaceholderViewDelegate.swift in Sources */, 08AF59F11E6D9BE800423EFF /* RGPageViewController+UIPageViewControllerDataSource.swift in Sources */, 0888D10A1F1E42A000A16863 /* CodeElementsSize.swift in Sources */, 08D120A21C937B2200A54ABC /* SortingQuizTableViewCell.swift in Sources */, @@ -11738,7 +11658,6 @@ 0846B1161EDDF63200D64D77 /* CodeTemplate.swift in Sources */, 08CB0D321FB5F9FC001A1E02 /* ExploreViewController.swift in Sources */, 089504841F27C5C600EEC939 /* FullHeightTableView.swift in Sources */, - 08D120CC1C937B2200A54ABC /* PlaceholderTestViewController.swift in Sources */, 08D120CD1C937B2200A54ABC /* StepicVideoPlayerViewController.swift in Sources */, 0829B83B1E9D05AE009B4A84 /* Certificate.swift in Sources */, 08D120CE1C937B2200A54ABC /* StringDatasetExtension.swift in Sources */, @@ -11835,7 +11754,6 @@ 0859AEA91E4F26C700A0D206 /* FillBlanksTextTableViewCell.swift in Sources */, 08D120F11C937B2200A54ABC /* Meta.swift in Sources */, 08D120F31C937B2200A54ABC /* StepicToken.swift in Sources */, - 08D120F41C937B2200A54ABC /* Styles.swift in Sources */, 08C1BF321FBA0CA1008F342F /* SearchResultsViewController.swift in Sources */, 080217841F55B1B200186245 /* Menu.swift in Sources */, 08D120F51C937B2200A54ABC /* SectionsViewController.swift in Sources */, @@ -11911,6 +11829,7 @@ 08AF59FC1E6D9BE800423EFF /* RGPageViewControllerDelegate.swift in Sources */, 086A9D621C74AF90003611DC /* GlobalFunctions.swift in Sources */, 08EB85E91D0F649700E4F345 /* CellOperationsUtil.swift in Sources */, + 866AD0D62061A7D70004C2B2 /* ControllerWithStepikPlaceholder.swift in Sources */, 08AF59F21E6D9BE800423EFF /* RGPageViewController+UIPageViewControllerDelegate.swift in Sources */, 0846B1101EDDEE8E00D64D77 /* CodeLimit+CoreDataProperties.swift in Sources */, 2C98B6B61FDFD74C005AB72C /* OnboardingViewController.swift in Sources */, @@ -11928,12 +11847,10 @@ 08CA59EA1BBD3D55008DC44D /* LabelExtension.swift in Sources */, 08FEFC211F127470005CA0FB /* AutocompleteWords.swift in Sources */, 0861E6721CD80A9600B45652 /* Executable.swift in Sources */, - 08970ECF1C6A326900846119 /* PlaceholderView.swift in Sources */, 083AABE91BE8D63D005E1E96 /* Progress.swift in Sources */, 86BB7C022019538100063538 /* CongratsView.swift in Sources */, 08E8F96C1F34DD48008CF4A1 /* SearchQueriesView.swift in Sources */, 0895A13B1E43836B00FE22DD /* FillBlanksReply.swift in Sources */, - 08970ED31C6A326900846119 /* PlaceholderStyleExtensions.swift in Sources */, 08F485AC1C580DB3000165AA /* MathQuizViewController.swift in Sources */, 088FD8171FB242B3008A2953 /* CourseSubscriber.swift in Sources */, 08B062DD1FDEFD5900A6C999 /* StreaksAlertPresentationManager.swift in Sources */, @@ -11991,6 +11908,7 @@ 080CE1461E9560EB0089A27F /* SectionsAPI.swift in Sources */, 083F2B1A1E9D920500714173 /* CertificatesPresenter.swift in Sources */, 08DF1D981BDAE11500BA35EA /* HTMLBuilder.swift in Sources */, + 866AD0D4206145DC0004C2B2 /* StepikPlaceholderStyle+Placeholders.swift in Sources */, 08EB85F51D10267800E4F345 /* WriteCommentDelegate.swift in Sources */, 08DF1D941BDADB2D00BA35EA /* Scripts.swift in Sources */, 0861E67B1CD9483500B45652 /* ExecutionQueues.swift in Sources */, @@ -12044,7 +11962,6 @@ 08EB85DF1D0F192900E4F345 /* LoadMoreTableViewCell.swift in Sources */, 08F5554E1C4F924000C877E8 /* Attempt.swift in Sources */, 08AC21481CDD558500FBB9CD /* PersistentUserTokenRecoveryManager.swift in Sources */, - 08970ED01C6A326900846119 /* PlaceholderViewDataSource.swift in Sources */, 2CB9E8C61F7E833F0004E17F /* NotificationsPagerViewController.swift in Sources */, 2CD8463F1F25F7F300E8153C /* Profile.swift in Sources */, 0800B81F1D06FDE5006C987E /* DiscussionProxiesAPI.swift in Sources */, @@ -12067,7 +11984,6 @@ 08B9770C1D19D5AA00FFC52C /* DiscussionWebTableViewCell.swift in Sources */, 08A0218E1D675B4700915679 /* SectionNavigationDelegate.swift in Sources */, 08CA59E61BBD25DC008DC44D /* TeachersTableViewCell.swift in Sources */, - 08970ED11C6A326900846119 /* PlaceholderViewDelegate.swift in Sources */, 08F485B71C58ECE3000165AA /* SortingQuizTableViewCell.swift in Sources */, 089A0DA71BE9FFCE004AF4EB /* UIViewExtensions.swift in Sources */, 0828FF6E1BC5D177000AFEA7 /* Section+CoreDataProperties.swift in Sources */, @@ -12198,7 +12114,6 @@ 08F485AA1C580D61000165AA /* MathReply.swift in Sources */, 08AF59F61E6D9BE800423EFF /* RGPageViewController+UIToolbarDelegate.swift in Sources */, 2CB62BDB2019ECB800B5E336 /* OnboardingCardStepViewController.swift in Sources */, - 08970ED71C6A36E500846119 /* PlaceholderTestViewController.swift in Sources */, 089C88DE1FCF494300003B63 /* CourseListType.swift in Sources */, 08EF9A081C91D0F800433E4A /* StepicVideoPlayerViewController.swift in Sources */, 08F555561C4F9FAB00C877E8 /* StringDatasetExtension.swift in Sources */, @@ -12292,7 +12207,6 @@ 083F2B171E9D8F1D00714173 /* CertificatesView.swift in Sources */, 080F31DD1BA7162C00F356A0 /* StepicToken.swift in Sources */, 080CE14F1E9562F30089A27F /* StepsAPI.swift in Sources */, - 08970ED21C6A326900846119 /* Styles.swift in Sources */, 0828FF791BC5E744000AFEA7 /* SectionsViewController.swift in Sources */, 08E3B9671EEA16DC0072995B /* CodeReply.swift in Sources */, 2CA9D9852010EEA2007AA743 /* AdaptiveRatingsAPI.swift in Sources */, @@ -12682,16 +12596,10 @@ 2C1B60EB1F4C4AEF00236804 /* RateAppAlertManager.swift in Sources */, 2C1B60EC1F4C4AEF00236804 /* Reachability.m in Sources */, 2C1B60ED1F4C4AEF00236804 /* ControllerHelperLaunchExtension.swift in Sources */, - 2C1B60EE1F4C4AEF00236804 /* PlaceholderTestViewController.swift in Sources */, - 2C1B60EF1F4C4AEF00236804 /* PlaceholderView.swift in Sources */, 2C1B60F01F4C4AEF00236804 /* CodeSnippetSymbols.swift in Sources */, - 2C1B60F11F4C4AEF00236804 /* PlaceholderViewDataSource.swift in Sources */, 2C49190B2062A1CC003E733D /* StepikTableView.swift in Sources */, 2C608AF12045691E006870BB /* StepReversedCardView.swift in Sources */, - 2C1B60F21F4C4AEF00236804 /* PlaceholderViewDelegate.swift in Sources */, 2C715DA220568BDD0098707D /* PinsMapView.swift in Sources */, - 2C1B60F31F4C4AEF00236804 /* Styles.swift in Sources */, - 2C1B60F41F4C4AEF00236804 /* PlaceholderStyleExtensions.swift in Sources */, 2C1B60F51F4C4AEF00236804 /* VideoDownloadView.swift in Sources */, 2C1B60F61F4C4AEF00236804 /* CodeSample.swift in Sources */, 2C1B60F71F4C4AEF00236804 /* AdaptiveRatingManager.swift in Sources */, @@ -12712,6 +12620,7 @@ 086FC6AE1FE04DBD00C7DFF4 /* RangeExtension.swift in Sources */, 089C88DB1FCCDF3900003B63 /* UserActivityHomeView.swift in Sources */, 2C47A168206284B1003E87EC /* NotificationRequestAlertContext.swift in Sources */, + 86A1C3392065173800D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */, 2C1B61061F4C4AEF00236804 /* Player.swift in Sources */, 2C1B61071F4C4AEF00236804 /* VideoRate.swift in Sources */, 2C1B61081F4C4AEF00236804 /* DownloadsViewController.swift in Sources */, @@ -12905,6 +12814,7 @@ 2C1B61AE1F4C4AEF00236804 /* VersionUpdateAlertConstructor.swift in Sources */, 2C1B61AF1F4C4AEF00236804 /* Version.swift in Sources */, 08CB0D531FB63F83001A1E02 /* ContentLanguagesView.swift in Sources */, + 86A1C334206515B300D0914C /* ControllerWithStepikPlaceholder.swift in Sources */, 2C1B61B01F4C4AEF00236804 /* StreaksNotificationSuggestionManager.swift in Sources */, 2C1B61B21F4C4AEF00236804 /* Executable.swift in Sources */, 2C608B282045699C006870BB /* AdaptiveCourseSelectPresenter.swift in Sources */, @@ -13165,16 +13075,10 @@ 2C1B62FC1F4C590700236804 /* RateAppAlertManager.swift in Sources */, 2C1B62FD1F4C590700236804 /* Reachability.m in Sources */, 2C1B62FE1F4C590700236804 /* ControllerHelperLaunchExtension.swift in Sources */, - 2C1B62FF1F4C590700236804 /* PlaceholderTestViewController.swift in Sources */, - 2C1B63001F4C590700236804 /* PlaceholderView.swift in Sources */, 2C1B63011F4C590700236804 /* CodeSnippetSymbols.swift in Sources */, - 2C1B63021F4C590700236804 /* PlaceholderViewDataSource.swift in Sources */, 2C49190C2062A1CC003E733D /* StepikTableView.swift in Sources */, 2C608AF22045691F006870BB /* StepReversedCardView.swift in Sources */, - 2C1B63031F4C590700236804 /* PlaceholderViewDelegate.swift in Sources */, 2C715DA120568BDD0098707D /* PinsMapView.swift in Sources */, - 2C1B63041F4C590700236804 /* Styles.swift in Sources */, - 2C1B63051F4C590700236804 /* PlaceholderStyleExtensions.swift in Sources */, 2C1B63061F4C590700236804 /* VideoDownloadView.swift in Sources */, 2C1B63071F4C590700236804 /* CodeSample.swift in Sources */, 2C1B63081F4C590700236804 /* AdaptiveRatingManager.swift in Sources */, @@ -13195,6 +13099,7 @@ 086FC6AF1FE04DBD00C7DFF4 /* RangeExtension.swift in Sources */, 089C88DC1FCCDF3900003B63 /* UserActivityHomeView.swift in Sources */, 2C47A169206284B1003E87EC /* NotificationRequestAlertContext.swift in Sources */, + 86A1C33A2065173900D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */, 2C1B63171F4C590700236804 /* Player.swift in Sources */, 2C1B63181F4C590700236804 /* VideoRate.swift in Sources */, 2C1B63191F4C590700236804 /* DownloadsViewController.swift in Sources */, @@ -13388,6 +13293,7 @@ 2C1B63BF1F4C590700236804 /* VersionUpdateAlertConstructor.swift in Sources */, 2C1B63C01F4C590700236804 /* Version.swift in Sources */, 08CB0D541FB63F83001A1E02 /* ContentLanguagesView.swift in Sources */, + 86A1C335206515B400D0914C /* ControllerWithStepikPlaceholder.swift in Sources */, 2C1B63C11F4C590700236804 /* StreaksNotificationSuggestionManager.swift in Sources */, 2C1B63C31F4C590700236804 /* Executable.swift in Sources */, 2C608B272045699C006870BB /* AdaptiveCourseSelectPresenter.swift in Sources */, @@ -13647,13 +13553,7 @@ 2C89A97F1F4C289900227C3B /* RateAppAlertManager.swift in Sources */, 2C89A9801F4C289900227C3B /* Reachability.m in Sources */, 2C89A9811F4C289900227C3B /* ControllerHelperLaunchExtension.swift in Sources */, - 2C89A9821F4C289900227C3B /* PlaceholderTestViewController.swift in Sources */, - 2C89A9831F4C289900227C3B /* PlaceholderView.swift in Sources */, 2C89A9841F4C289900227C3B /* CodeSnippetSymbols.swift in Sources */, - 2C89A9851F4C289900227C3B /* PlaceholderViewDataSource.swift in Sources */, - 2C89A9861F4C289900227C3B /* PlaceholderViewDelegate.swift in Sources */, - 2C89A9871F4C289900227C3B /* Styles.swift in Sources */, - 2C89A9881F4C289900227C3B /* PlaceholderStyleExtensions.swift in Sources */, 2C89A9891F4C289900227C3B /* VideoDownloadView.swift in Sources */, 2C89A98A1F4C289900227C3B /* CodeSample.swift in Sources */, 2C89A98B1F4C289900227C3B /* AdaptiveRatingManager.swift in Sources */, @@ -13733,6 +13633,7 @@ 2C89A9C91F4C289900227C3B /* RGPageViewController+UICollectionViewDelegate.swift in Sources */, 2C89A9CA1F4C289900227C3B /* RGPageViewController+UICollectionViewDelegateFlowLayout.swift in Sources */, 2C89A9CB1F4C289900227C3B /* CodeTemplate+CoreDataProperties.swift in Sources */, + 86A1C3372065173700D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */, 088EDCAA1F9496DE0098DEC7 /* CourseListHorizontalViewController.swift in Sources */, 2C6A76392045CE8200C509A6 /* CardStepDelegate.swift in Sources */, 2C89A9CC1F4C289900227C3B /* RGPageViewController+UIPageViewControllerDataSource.swift in Sources */, @@ -13972,6 +13873,7 @@ 2C89AA8F1F4C289900227C3B /* User+CoreDataProperties.swift in Sources */, 2C89AA901F4C289900227C3B /* User.swift in Sources */, 2C89AA921F4C289900227C3B /* Parser.swift in Sources */, + 86A1C332206515B200D0914C /* ControllerWithStepikPlaceholder.swift in Sources */, 089C88E21FCF494300003B63 /* CourseListType.swift in Sources */, 2C89AA931F4C289900227C3B /* StepicToken.swift in Sources */, 2C89AA941F4C289900227C3B /* AuthInfo.swift in Sources */, @@ -14130,16 +14032,10 @@ 2C9731331F4C38F600AC9301 /* RateAppAlertManager.swift in Sources */, 2C9731341F4C38F600AC9301 /* Reachability.m in Sources */, 2C9731351F4C38F600AC9301 /* ControllerHelperLaunchExtension.swift in Sources */, - 2C9731361F4C38F600AC9301 /* PlaceholderTestViewController.swift in Sources */, - 2C9731371F4C38F600AC9301 /* PlaceholderView.swift in Sources */, 2C9731381F4C38F600AC9301 /* CodeSnippetSymbols.swift in Sources */, - 2C9731391F4C38F600AC9301 /* PlaceholderViewDataSource.swift in Sources */, 2C49190A2062A1CC003E733D /* StepikTableView.swift in Sources */, 2C608AF02045691E006870BB /* StepReversedCardView.swift in Sources */, - 2C97313A1F4C38F600AC9301 /* PlaceholderViewDelegate.swift in Sources */, 2C715DA320568BDE0098707D /* PinsMapView.swift in Sources */, - 2C97313B1F4C38F600AC9301 /* Styles.swift in Sources */, - 2C97313C1F4C38F600AC9301 /* PlaceholderStyleExtensions.swift in Sources */, 2C97313D1F4C38F600AC9301 /* VideoDownloadView.swift in Sources */, 2C97313E1F4C38F600AC9301 /* CodeSample.swift in Sources */, 2C97313F1F4C38F600AC9301 /* AdaptiveRatingManager.swift in Sources */, @@ -14160,6 +14056,7 @@ 086FC6AD1FE04DBD00C7DFF4 /* RangeExtension.swift in Sources */, 089C88DA1FCCDF3900003B63 /* UserActivityHomeView.swift in Sources */, 2C47A167206284B1003E87EC /* NotificationRequestAlertContext.swift in Sources */, + 86A1C3382065173800D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */, 2C97314E1F4C38F600AC9301 /* Player.swift in Sources */, 2C97314F1F4C38F600AC9301 /* VideoRate.swift in Sources */, 2C9731501F4C38F600AC9301 /* DownloadsViewController.swift in Sources */, @@ -14353,6 +14250,7 @@ 2C9731F71F4C38F600AC9301 /* Version.swift in Sources */, 08CB0D521FB63F83001A1E02 /* ContentLanguagesView.swift in Sources */, 2C9731F81F4C38F600AC9301 /* StreaksNotificationSuggestionManager.swift in Sources */, + 86A1C333206515B300D0914C /* ControllerWithStepikPlaceholder.swift in Sources */, 2C9731FA1F4C38F600AC9301 /* Executable.swift in Sources */, 2C608B292045699C006870BB /* AdaptiveCourseSelectPresenter.swift in Sources */, 2C9731FB1F4C38F600AC9301 /* ExecutionQueue.swift in Sources */, @@ -14682,13 +14580,7 @@ 864514F41E9FD4F6007F73BE /* RateAppAlertManager.swift in Sources */, 864D67D61E83E394001E8D9E /* Reachability.m in Sources */, 864D67D51E83E23B001E8D9E /* ControllerHelperLaunchExtension.swift in Sources */, - 864D67411E83E08E001E8D9E /* PlaceholderTestViewController.swift in Sources */, - 864D67431E83E08E001E8D9E /* PlaceholderView.swift in Sources */, 085E9E6B1F138C1A00D6A4BC /* CodeSnippetSymbols.swift in Sources */, - 864D67441E83E08E001E8D9E /* PlaceholderViewDataSource.swift in Sources */, - 864D67451E83E08E001E8D9E /* PlaceholderViewDelegate.swift in Sources */, - 864D67461E83E08E001E8D9E /* Styles.swift in Sources */, - 864D67471E83E08E001E8D9E /* PlaceholderStyleExtensions.swift in Sources */, 864D67481E83E08E001E8D9E /* VideoDownloadView.swift in Sources */, 080C5E7D1EFC13ED0036EB3D /* CodeSample.swift in Sources */, 2CA2E72620237E55001DC410 /* AdaptiveRatingsViewController.swift in Sources */, @@ -14719,6 +14611,7 @@ 864D675B1E83E08E001E8D9E /* Player.swift in Sources */, 2CA2E72320237E4A001DC410 /* AdaptiveStatsPresenter.swift in Sources */, 864D675C1E83E08E001E8D9E /* VideoRate.swift in Sources */, + 86A1C3362065173700D0914C /* StepikPlaceholderStyle+Placeholders.swift in Sources */, 864D675D1E83E08E001E8D9E /* DownloadsViewController.swift in Sources */, 864D675E1E83E08E001E8D9E /* DownloadTableViewCell.swift in Sources */, 864D67611E83E08E001E8D9E /* DiscussionWebTableViewCell.swift in Sources */, @@ -15038,6 +14931,7 @@ 864D67111E83DE03001E8D9E /* StepicsAPI.swift in Sources */, 086D5B54201283C2000F7715 /* TooltipFactory.swift in Sources */, 2CA2E71720237E0E001DC410 /* StepReversedCardView.swift in Sources */, + 86A1C331206515B200D0914C /* ControllerWithStepikPlaceholder.swift in Sources */, 864D67121E83DE03001E8D9E /* UnitsAPI.swift in Sources */, 864D67131E83DE03001E8D9E /* UserActivitiesAPI.swift in Sources */, 089C88E01FCF494300003B63 /* CourseListType.swift in Sources */, diff --git a/Stepic/Base.lproj/Main.storyboard b/Stepic/Base.lproj/Main.storyboard index 85f3ed671a..d399bdc72e 100644 --- a/Stepic/Base.lproj/Main.storyboard +++ b/Stepic/Base.lproj/Main.storyboard @@ -254,7 +254,7 @@ - + @@ -297,7 +297,7 @@ - + @@ -690,6 +690,6 @@ - + diff --git a/Stepic/CardsStepsViewController.swift b/Stepic/CardsStepsViewController.swift index 279a106184..4b2aa21446 100644 --- a/Stepic/CardsStepsViewController.swift +++ b/Stepic/CardsStepsViewController.swift @@ -10,8 +10,9 @@ import Foundation import PromiseKit import Koloda -class CardsStepsViewController: UIViewController, CardsStepsView { +class CardsStepsViewController: UIViewController, CardsStepsView, ControllerWithStepikPlaceholder { + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() var presenter: CardsStepsPresenter? @IBOutlet weak var kolodaView: KolodaView! @@ -25,34 +26,32 @@ class CardsStepsViewController: UIViewController, CardsStepsView { didSet { switch state { case .normal: - self.placeholderView.isHidden = true - self.kolodaView.isHidden = false - case .connectionError, .coursePassed: - self.placeholderView.isHidden = false - self.kolodaView.isHidden = true - - self.placeholderView.datasource = self + isPlaceholderShown = false + case .connectionError: + showPlaceholder(for: .connectionError) + case .coursePassed: + showPlaceholder(for: .adaptiveCoursePassed) default: break } } } - lazy var placeholderView: PlaceholderView = { - let v = PlaceholderView() - self.view.insertSubview(v, aboveSubview: self.view) - v.align(toView: self.kolodaView) - v.delegate = self - v.datasource = self - v.backgroundColor = self.view.backgroundColor - return v - }() - // Can be overriden in the children classes (for adaptive app) var cardView: StepCardView { return StepCardView() } + override func viewDidLoad() { + super.viewDidLoad() + + registerPlaceholder(placeholder: StepikPlaceholder(.noConnection, action: { [weak self] in + self?.presenter?.tryAgain() + }), for: .connectionError) + + registerPlaceholder(placeholder: StepikPlaceholder(.adaptiveCoursePassed), for: .adaptiveCoursePassed) + } + func refreshCards() { if kolodaView.delegate == nil { kolodaView.dataSource = self @@ -199,62 +198,6 @@ extension CardsStepsViewController: KolodaViewDataSource { } } -extension CardsStepsViewController: PlaceholderViewDataSource { - func placeholderImage() -> UIImage? { - switch state { - case .connectionError: - return Images.placeholders.connectionError - case .coursePassed: - return Images.placeholders.coursePassed - default: - return nil - } - } - - func placeholderButtonTitle() -> String? { - switch state { - case .connectionError: - return NSLocalizedString("TryAgain", comment: "") - default: - return nil - } - } - - func placeholderDescription() -> String? { - switch state { - case .connectionError: - return nil - case .coursePassed: - return NSLocalizedString("NoRecommendations", comment: "") - default: - return nil - } - } - - func placeholderStyle() -> PlaceholderStyle { - var style = PlaceholderStyle() - style.button.textColor = UIColor.mainDark - return style - } - - func placeholderTitle() -> String? { - switch state { - case .connectionError: - return NSLocalizedString("ConnectionErrorText", comment: "") - case .coursePassed: - return NSLocalizedString("CoursePassed", comment: "") - default: - return nil - } - } -} - -extension CardsStepsViewController: PlaceholderViewDelegate { - func placeholderButtonDidPress() { - presenter?.tryAgain() - } -} - extension CardsStepsViewController: CardStepDelegate { func stepSubmissionDidCorrect() { AnalyticsReporter.reportEvent(AnalyticsEvents.Adaptive.Step.correctAnswer) diff --git a/Stepic/CertificatesStoryboard.storyboard b/Stepic/CertificatesStoryboard.storyboard index 50ad83285c..013194212c 100644 --- a/Stepic/CertificatesStoryboard.storyboard +++ b/Stepic/CertificatesStoryboard.storyboard @@ -34,7 +34,7 @@ - + diff --git a/Stepic/CertificatesViewController.swift b/Stepic/CertificatesViewController.swift index 1878eacb22..a335c552a8 100644 --- a/Stepic/CertificatesViewController.swift +++ b/Stepic/CertificatesViewController.swift @@ -7,24 +7,17 @@ // import Foundation -import DZNEmptyDataSet -enum CertificatesEmptyDatasetState { - case anonymous, error, empty, refreshing -} +class CertificatesViewController: UIViewController, CertificatesView, ControllerWithStepikPlaceholder { + + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() -class CertificatesViewController: UIViewController, CertificatesView { - @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var tableView: StepikTableView! var presenter: CertificatesPresenter? var certificates: [CertificateViewData] = [] var showNextPageFooter: Bool = false - var emptyState: CertificatesEmptyDatasetState = .empty { - didSet { - tableView.reloadEmptyDataSet() - } - } let refreshControl = UIRefreshControl() @@ -33,14 +26,26 @@ class CertificatesViewController: UIViewController, CertificatesView { tableView.delegate = self tableView.dataSource = self + tableView.emptySetPlaceholder = StepikPlaceholder(.emptyCertificates) { [weak self] in + self?.tabBarController?.selectedIndex = 1 + } + tableView.loadingPlaceholder = StepikPlaceholder(.emptyCertificatesLoading) + + registerPlaceholder(placeholder: StepikPlaceholder(.login, action: { [weak self] in + guard let strongSelf = self else { + return + } + RoutingManager.auth.routeFrom(controller: strongSelf, success: nil, cancel: nil) + }), for: .anonymous) + registerPlaceholder(placeholder: StepikPlaceholder(.noConnection, action: { [weak self] in + self?.presenter?.checkStatus() + }), for: .connectionError) + title = NSLocalizedString("Certificates", comment: "") presenter = CertificatesPresenter(certificatesAPI: ApiDataDownloader.certificates, coursesAPI: ApiDataDownloader.courses, presentationContainer: PresentationContainer.certificates, view: self) presenter?.view = self - tableView.emptyDataSetSource = self - tableView.emptyDataSetDelegate = self - tableView.register(UINib(nibName: "CertificateTableViewCell", bundle: nil), forCellReuseIdentifier: "CertificateTableViewCell") self.tableView.estimatedRowHeight = 161 @@ -125,21 +130,23 @@ class CertificatesViewController: UIViewController, CertificatesView { func displayAnonymous() { refreshControl.endRefreshing() - emptyState = .anonymous + showPlaceholder(for: .anonymous) } func displayError() { refreshControl.endRefreshing() - emptyState = .error + showPlaceholder(for: .connectionError) } func displayEmpty() { refreshControl.endRefreshing() - emptyState = .empty + tableView.reloadData() + self.isPlaceholderShown = false } func displayRefreshing() { - emptyState = .refreshing + tableView.showLoadingPlaceholder() + self.isPlaceholderShown = false } func displayLoadNextPageError() { @@ -172,30 +179,6 @@ class CertificatesViewController: UIViewController, CertificatesView { } } -extension CertificatesViewController : DZNEmptyDataSetDelegate { - func emptyDataSetShouldAllowScroll(_ scrollView: UIScrollView!) -> Bool { - return true - } - - func emptyDataSetDidTapButton(_ scrollView: UIScrollView!) { - switch emptyState { - case .anonymous: - RoutingManager.auth.routeFrom(controller: self, success: nil, cancel: nil) - break - - case .empty: - self.tabBarController?.selectedIndex = 1 - break - - case .error: - break - - case .refreshing: - break - } - } -} - extension CertificatesViewController : UITableViewDelegate { func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { @@ -272,96 +255,3 @@ extension CertificatesViewController : UITableViewDataSource { return cell } } - -extension CertificatesViewController : DZNEmptyDataSetSource { - func image(forEmptyDataSet scrollView: UIScrollView!) -> UIImage! { - //Add correct placeholders here - switch emptyState { - case .anonymous: - return Images.placeholders.anonymous - case .empty: - return Images.placeholders.certificates - case .error: - return Images.placeholders.connectionError - case .refreshing: - return Images.placeholders.certificates - } - } - - func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - - switch emptyState { - case .anonymous: - text = NSLocalizedString("AnonymousCertificatesTitle", comment: "") - case .empty: - text = NSLocalizedString("EmptyCertificatesTitle", comment: "") - break - case .error: - text = NSLocalizedString("ConnectionErrorTitle", comment: "") - break - case .refreshing: - text = NSLocalizedString("Refreshing", comment: "") - break - } - - let attributes = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 18.0), - NSAttributedStringKey.foregroundColor: UIColor.darkGray] - - return NSAttributedString(string: text, attributes: attributes) - } - - func description(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - - switch emptyState { - case .anonymous: - text = NSLocalizedString("SignInToHaveCertificates", comment: "") - break - case .empty: - text = NSLocalizedString("EmptyCertificatesDescription", comment: "") - break - case .error: - text = NSLocalizedString("ConnectionErrorPullToRefresh", comment: "") - break - case .refreshing: - text = NSLocalizedString("RefreshingDescription", comment: "") - break - } - - let paragraph = NSMutableParagraphStyle() - paragraph.lineBreakMode = .byWordWrapping - paragraph.alignment = .center - - let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14.0), - NSAttributedStringKey.foregroundColor: UIColor.lightGray, - NSAttributedStringKey.paragraphStyle: paragraph] - - return NSAttributedString(string: text, attributes: attributes) - } - - func buttonTitle(forEmptyDataSet scrollView: UIScrollView!, for state: UIControlState) -> NSAttributedString! { - var text: String = "" - switch emptyState { - case .anonymous: - text = NSLocalizedString("SignIn", comment: "") - case .empty: - text = NSLocalizedString("ChooseCourse", comment: "") - case .error: - text = "" - break - case .refreshing: - text = "" - break - } - - let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16.0), - NSAttributedStringKey.foregroundColor: UIColor.mainDark] - - return NSAttributedString(string: text, attributes: attributes) - } - - func backgroundColor(forEmptyDataSet scrollView: UIScrollView!) -> UIColor! { - return UIColor.groupTableViewBackground - } -} diff --git a/Stepic/ControllerWithStepikPlaceholder.swift b/Stepic/ControllerWithStepikPlaceholder.swift new file mode 100644 index 0000000000..2a79724da9 --- /dev/null +++ b/Stepic/ControllerWithStepikPlaceholder.swift @@ -0,0 +1,106 @@ +// +// ControllerWithStepikPlaceholder.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 20.03.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import UIKit + +typealias StepikPlaceholderControllerState = StepikPlaceholderControllerContainer.PlaceholderState + +class StepikPlaceholderControllerContainer: StepikPlaceholderViewDelegate { + static let shared = StepikPlaceholderControllerContainer() + + open class PlaceholderState: Equatable, Hashable { + var id: String + + init(id: String) { + self.id = id + } + + static let anonymous = PlaceholderState(id: "anonymous") + static let connectionError = PlaceholderState(id: "connectionError") + static let refreshing = PlaceholderState(id: "refreshing") + static let adaptiveCoursePassed = PlaceholderState(id: "adaptiveCoursePassed") + + var hashValue: Int { + get { + return id.hashValue + } + } + + public static func == (lhs: PlaceholderState, rhs: PlaceholderState) -> Bool { + return lhs.id == rhs.id + } + } + + var registeredPlaceholders: [PlaceholderState: StepikPlaceholder] = [:] + var currentPlaceholderButtonAction: (() -> Void)? + var isPlaceholderShown: Bool = false + + lazy var placeholderView: StepikPlaceholderView = { + let view = StepikPlaceholderView() + return view + }() + + func buttonDidClick(_ button: UIButton) { + currentPlaceholderButtonAction?() + } +} + +protocol ControllerWithStepikPlaceholder: class { + var isPlaceholderShown: Bool { get set } + var placeholderContainer: StepikPlaceholderControllerContainer { get set } + + func registerPlaceholder(placeholder: StepikPlaceholder, for state: StepikPlaceholderControllerState) + func showPlaceholder(for state: StepikPlaceholderControllerState) +} + +extension ControllerWithStepikPlaceholder where Self: UIViewController { + var isPlaceholderShown: Bool { + set { + placeholderContainer.placeholderView.isHidden = !newValue + placeholderContainer.isPlaceholderShown = newValue + } + get { + return placeholderContainer.isPlaceholderShown + } + } + + func registerPlaceholder(placeholder: StepikPlaceholder, for state: StepikPlaceholderControllerState) { + placeholderContainer.registeredPlaceholders[state] = placeholder + } + + func showPlaceholder(for state: StepikPlaceholderControllerState) { + guard let placeholder = placeholderContainer.registeredPlaceholders[state] else { + return + } + + updatePlaceholderLayout() + placeholderContainer.placeholderView.set(placeholder: placeholder.style) + placeholderContainer.placeholderView.delegate = placeholderContainer + placeholderContainer.currentPlaceholderButtonAction = placeholder.buttonAction + + isPlaceholderShown = true + } + + private func updatePlaceholderLayout() { + guard let view = self.view else { + return + } + + if placeholderContainer.placeholderView.superview == nil { + placeholderContainer.placeholderView.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(placeholderContainer.placeholderView) + placeholderContainer.placeholderView.alignCenter(withView: view) + placeholderContainer.placeholderView.align(toView: view) + + placeholderContainer.placeholderView.setNeedsLayout() + placeholderContainer.placeholderView.layoutIfNeeded() + } + view.bringSubview(toFront: placeholderContainer.placeholderView) + } +} diff --git a/Stepic/DiscussionsViewController.swift b/Stepic/DiscussionsViewController.swift index 3a46b2794d..6b39c3afc9 100644 --- a/Stepic/DiscussionsViewController.swift +++ b/Stepic/DiscussionsViewController.swift @@ -8,7 +8,6 @@ import UIKit import SDWebImage -import DZNEmptyDataSet enum DiscussionsEmptyDataSetState { case error, empty, none @@ -38,13 +37,14 @@ struct DiscussionsCellInfo { } } -class DiscussionsViewController: UIViewController { +class DiscussionsViewController: UIViewController, ControllerWithStepikPlaceholder { + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() var discussionProxyId: String! var target: Int! var step: Step! - @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var tableView: StepikTableView! var refreshControl: UIRefreshControl? = UIRefreshControl() @@ -52,7 +52,16 @@ class DiscussionsViewController: UIViewController { var emptyDatasetState: DiscussionsEmptyDataSetState = .none { didSet { - tableView.reloadEmptyDataSet() + switch emptyDatasetState { + case .none: + isPlaceholderShown = false + tableView.showLoadingPlaceholder() + case .empty: + isPlaceholderShown = false + tableView.reloadData() + case .error: + showPlaceholder(for: .connectionError) + } } } @@ -61,10 +70,15 @@ class DiscussionsViewController: UIViewController { print("did load") + registerPlaceholder(placeholder: StepikPlaceholder(.noConnection, action: { [weak self] in + self?.reloadDiscussions() + }), for: .connectionError) + tableView.delegate = self tableView.dataSource = self - tableView.emptyDataSetSource = self - tableView.emptyDataSetDelegate = self + tableView.emptySetPlaceholder = StepikPlaceholder(.emptyDiscussions) + tableView.loadingPlaceholder = StepikPlaceholder(.emptyDiscussionsLoading) + emptyDatasetState = .none self.tableView.rowHeight = UITableViewAutomaticDimension @@ -272,10 +286,7 @@ class DiscussionsViewController: UIViewController { UIThread.performUI({ [weak self] in if self?.cellsInfo.count == 0 { - self?.tableView.emptyDataSetSource = self self?.emptyDatasetState = emptyState - } else { - self?.tableView.emptyDataSetSource = nil } self?.tableView.reloadData() }) @@ -729,71 +740,3 @@ extension DiscussionsViewController : WriteCommentDelegate { } } } - -extension DiscussionsViewController : DZNEmptyDataSetSource, DZNEmptyDataSetDelegate { - func image(forEmptyDataSet scrollView: UIScrollView!) -> UIImage! { - switch emptyDatasetState { - case .empty: - return Images.noCommentsWhite.size200x200 - case .error: - return Images.noWifiImage.white - case .none: - return Images.noCommentsWhite.size200x200 - } - } - - func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - switch emptyDatasetState { - case .empty: - text = NSLocalizedString("NoDiscussionsTitle", comment: "") - break - case .error: - text = NSLocalizedString("ConnectionErrorTitle", comment: "") - break - case .none: - text = "" - break - } - - let attributes = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 18.0), - NSAttributedStringKey.foregroundColor: UIColor.darkGray] - - return NSAttributedString(string: text, attributes: attributes) - } - - func description(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - - switch emptyDatasetState { - case .empty: - text = NSLocalizedString("NoDiscussionsDescription", comment: "") - break - case .error: - text = NSLocalizedString("ConnectionErrorPullToRefresh", comment: "") - break - case .none: - text = NSLocalizedString("RefreshingDiscussions", comment: "") - break - } - - let paragraph = NSMutableParagraphStyle() - paragraph.lineBreakMode = .byWordWrapping - paragraph.alignment = .center - - let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14.0), - NSAttributedStringKey.foregroundColor: UIColor.lightGray, - NSAttributedStringKey.paragraphStyle: paragraph] - - return NSAttributedString(string: text, attributes: attributes) - } - - func verticalOffset(forEmptyDataSet scrollView: UIScrollView!) -> CGFloat { - // print("offset -> \((self.navigationController?.navigationBar.bounds.height) ?? 0 + UIApplication.sharedApplication().statusBarFrame.height)") - return 0 - } - - func emptyDataSetShouldAllowScroll(_ scrollView: UIScrollView!) -> Bool { - return true - } -} diff --git a/Stepic/DiscussionsViewController.xib b/Stepic/DiscussionsViewController.xib index 85de456107..10d2564cc4 100644 --- a/Stepic/DiscussionsViewController.xib +++ b/Stepic/DiscussionsViewController.xib @@ -1,15 +1,14 @@ - + - - + - + @@ -17,10 +16,10 @@ - + - + diff --git a/Stepic/DownloadsViewController.swift b/Stepic/DownloadsViewController.swift index f0e82490fa..ae6dfa8c26 100644 --- a/Stepic/DownloadsViewController.swift +++ b/Stepic/DownloadsViewController.swift @@ -276,7 +276,6 @@ extension DownloadsViewController : VideoDownloadDelegate { tableView.deleteSections(IndexSet(integer: 0), with: .automatic) } self.tableView.endUpdates() - self.tableView.reloadEmptyDataSet() } } @@ -302,7 +301,6 @@ extension DownloadsViewController : VideoDownloadDelegate { tableView.deleteSections(IndexSet(integer: (isSectionDownloading(0) ? 1 : 0)), with: .automatic) } self.tableView.endUpdates() - self.tableView.reloadEmptyDataSet() } } diff --git a/Stepic/MenuViewController.swift b/Stepic/MenuViewController.swift index 6ee1cedfe7..3eca8468b0 100644 --- a/Stepic/MenuViewController.swift +++ b/Stepic/MenuViewController.swift @@ -11,7 +11,7 @@ import FLKAutoLayout class MenuViewController: UIViewController { - let tableView: UITableView = UITableView() + let tableView: StepikTableView = StepikTableView() var interfaceManager: MenuUIManager? var menu: Menu? { diff --git a/Stepic/NotificationsPagerViewController.swift b/Stepic/NotificationsPagerViewController.swift index 3f5d737f4a..2a401013c2 100644 --- a/Stepic/NotificationsPagerViewController.swift +++ b/Stepic/NotificationsPagerViewController.swift @@ -8,21 +8,13 @@ import UIKit -class NotificationsPagerViewController: PagerController { +class NotificationsPagerViewController: PagerController, ControllerWithStepikPlaceholder { + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() + var sections: [NotificationsSection] = [ .all, .learning, .comments, .reviews, .teaching, .other ] - lazy var placeholderView: UIView = { - let v = PlaceholderView() - self.view.addSubview(v) - v.align(toView: self.view) - v.delegate = self - v.datasource = self - v.backgroundColor = UIColor.groupTableViewBackground - return v - }() - override func viewDidLoad() { super.viewDidLoad() @@ -31,14 +23,27 @@ class NotificationsPagerViewController: PagerController { self.dataSource = self setUpTabs() + registerPlaceholder(placeholder: StepikPlaceholder(.login, action: { [weak self] in + guard let strongSelf = self else { + return + } + + RoutingManager.auth.routeFrom(controller: strongSelf, success: nil, cancel: nil) + }), for: .anonymous) + if !AuthInfo.shared.isAuthorized { - placeholderView.isHidden = false + showPlaceholder(for: .anonymous) } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - placeholderView.isHidden = AuthInfo.shared.isAuthorized + + if AuthInfo.shared.isAuthorized { + isPlaceholderShown = false + } else { + showPlaceholder(for: .anonymous) + } updateNavigationControllerShadow(show: !AuthInfo.shared.isAuthorized) } @@ -103,34 +108,3 @@ extension NotificationsPagerViewController: UINavigationControllerDelegate { } } } - -extension NotificationsPagerViewController: PlaceholderViewDataSource { - func placeholderImage() -> UIImage? { - return Images.placeholders.anonymous - } - - func placeholderButtonTitle() -> String? { - return NSLocalizedString("SignIn", comment: "") - } - - func placeholderDescription() -> String? { - return NSLocalizedString("SignInToHaveNotifications", comment: "") - } - - func placeholderStyle() -> PlaceholderStyle { - var style = stepicPlaceholderStyle - style.button.backgroundColor = .clear - style.title.textColor = UIColor.darkGray - return style - } - - func placeholderTitle() -> String? { - return NSLocalizedString("AnonymousNotificationsTitle", comment: "") - } -} - -extension NotificationsPagerViewController: PlaceholderViewDelegate { - func placeholderButtonDidPress() { - RoutingManager.auth.routeFrom(controller: self, success: nil, cancel: nil) - } -} diff --git a/Stepic/NotificationsViewController.swift b/Stepic/NotificationsViewController.swift index 9a7b929ebb..45a0ecd589 100644 --- a/Stepic/NotificationsViewController.swift +++ b/Stepic/NotificationsViewController.swift @@ -28,7 +28,6 @@ class NotificationsViewController: UIViewController, NotificationsView { case .empty: self.refreshControl.endRefreshing() self.tableView.tableFooterView = UIView() - self.tableView.reloadEmptyDataSet() } } } diff --git a/Stepic/PlaceholderStyleExtensions.swift b/Stepic/PlaceholderStyleExtensions.swift deleted file mode 100644 index 2dcb3cf30c..0000000000 --- a/Stepic/PlaceholderStyleExtensions.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// PlaceholderStyleExtensions.swift -// OstrenkiyPlaceholderView -// -// Created by Alexander Karpov on 08.02.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import UIKit - -extension UIButton { - func implementStyle(_ style: PlaceholderStyle.ButtonStyle) { - self.titleLabel?.font = style.font - self.setTitleColor(style.textColor, for: UIControlState()) - switch style.borderType { - case .rect : - self.setRoundedCorners(cornerRadius: 0.0, borderWidth: 1.0, borderColor: style.borderColor) - break - case .rounded: - self.setRoundedCorners(cornerRadius: 8.0, borderWidth: 1.0, borderColor: style.borderColor) - break - case .none: - break - } - self.backgroundColor = style.backgroundColor - } -} - -extension UILabel { - func implementStyle(_ style: PlaceholderStyle.LabelStyle) { - self.font = style.font - self.textColor = style.textColor - self.textAlignment = style.textAlignment - self.lineBreakMode = style.lineBreakMode - } - - class func heightForLabelWithText(_ text: String, style: PlaceholderStyle.LabelStyle, width: CGFloat) -> CGFloat { - let label = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude)) - - label.numberOfLines = 0 - - label.text = text - - label.implementStyle(style) - label.sizeToFit() - - // print(label.bounds.height) - return label.bounds.height - } - -} diff --git a/Stepic/PlaceholderTestViewController.swift b/Stepic/PlaceholderTestViewController.swift deleted file mode 100644 index 15fe5faabb..0000000000 --- a/Stepic/PlaceholderTestViewController.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// PlaceholderTestViewController.swift -// OstrenkiyPlaceholderView -// -// Created by Alexander Karpov on 09.02.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import UIKit -import FLKAutoLayout - -class PlaceholderTestViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - let placeholderView = PlaceholderView() -// placeholderView.backgroundColor = UIColor.greenColor() - self.view.addSubview(placeholderView) - placeholderView.alignTop("16", leading: "16", bottom: "-16", trailing: "-16", toView: self.view) - placeholderView.delegate = self - placeholderView.datasource = self - - // Do any additional setup after loading the view, typically from a nib. - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - -} - -extension PlaceholderTestViewController : PlaceholderViewDelegate { - func placeholderButtonDidPress() { - print("did press placeholder button") - } -} - -extension PlaceholderTestViewController : PlaceholderViewDataSource { - func placeholderButtonTitle() -> String? { - return nil -// return "Try again" - } - - func placeholderStyle() -> PlaceholderStyle { - return stepicPlaceholderStyle - } - - func placeholderDescription() -> String? { - return "Failed to connect to the Internet. Press the button to retry or go fuck yourself" - } - - func placeholderImage() -> UIImage? { - return nil - } - - func placeholderTitle() -> String? { - return "Connection error!" - } -} diff --git a/Stepic/PlaceholderView.storyboard b/Stepic/PlaceholderView.storyboard deleted file mode 100644 index 313549f2fa..0000000000 --- a/Stepic/PlaceholderView.storyboard +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Stepic/PlaceholderView.swift b/Stepic/PlaceholderView.swift deleted file mode 100644 index c8e309a35c..0000000000 --- a/Stepic/PlaceholderView.swift +++ /dev/null @@ -1,241 +0,0 @@ -// -// PlaceholderView.swift -// OstrenkiyPlaceholderView -// -// Created by Alexander Karpov on 02.02.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import UIKit -import FLKAutoLayout - -class PlaceholderView: UIView { - - /* - // Only override drawRect: if you perform custom drawing. - // An empty implementation adversely affects performance during animation. - override func drawRect(rect: CGRect) { - // Drawing code - } - */ - - fileprivate var bottomElement: UIView? - - fileprivate var middleView: UIView! - fileprivate var middleViewHeight: NSLayoutConstraint! - - fileprivate var imageView: UIImageView? - fileprivate var imageViewHeight: NSLayoutConstraint? - fileprivate var imageViewWidth: NSLayoutConstraint? - - fileprivate var titleLabel: UILabel? - fileprivate var titleLabelHeight: NSLayoutConstraint? - - fileprivate var descriptionLabel: UILabel? - fileprivate var descriptionLabelHeight: NSLayoutConstraint? - - fileprivate var button: UIButton? - fileprivate var buttonHeight: NSLayoutConstraint? - - fileprivate func addMiddleView() { - middleView = UIView() - self.addSubview(middleView) - self.bringSubview(toFront: middleView) - middleView.alignLeading("0", trailing: "0", toView: self) - middleView.alignCenterY(withView: self, predicate: "0") - middleViewHeight = middleView.constrainHeight("0") - middleView.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 999), for: .vertical) - setNeedsLayout() - layoutIfNeeded() - } - - fileprivate func setUpVerticalConstraints(_ view: UIView) { - if let b = bottomElement { - view.constrainTopSpace(toView: b, predicate: "16") - middleViewHeight.constant += 16 - } else { - view.alignTopEdge(withView: middleView, predicate: "0") - } - - bottomElement = view - } - - fileprivate func addImage(_ image: UIImage) { - imageView = UIImageView(frame: CGRect.zero) - middleView.addSubview(imageView!) - middleView.bringSubview(toFront: imageView!) - setUpVerticalConstraints(imageView!) - imageView?.image = image - imageViewHeight = imageView!.constrainHeight("\(image.size.height)") - imageViewWidth = imageView!.constrainWidth("\(image.size.width)") - _ = imageView?.alignCenterX(withView: middleView, predicate: "0") - } - - fileprivate func addTitle(_ title: String) { - titleLabel = UILabel(frame: CGRect.zero) - titleLabel?.text = title - titleLabel?.numberOfLines = 0 - - middleView.addSubview(titleLabel!) - middleView.bringSubview(toFront: titleLabel!) - setUpVerticalConstraints(titleLabel!) - _ = titleLabel?.alignLeading("8", trailing: "-8", toView: middleView) - - if let style = datasource?.placeholderStyle() { - titleLabel?.implementStyle(style.title) - titleLabelHeight = titleLabel?.constrainHeight("\(UILabel.heightForLabelWithText(title, style: style.title, width: middleView.bounds.width - 16))") - } else { - titleLabelHeight = titleLabel?.constrainHeight("30") - } - - //TODO: Add title style implementation here - } - - fileprivate func addDescription(_ desc: String) { - descriptionLabel = UILabel(frame: CGRect.zero) - descriptionLabel?.text = desc - descriptionLabel?.numberOfLines = 0 - - middleView.addSubview(descriptionLabel!) - middleView.bringSubview(toFront: descriptionLabel!) - setUpVerticalConstraints(descriptionLabel!) - _ = descriptionLabel?.alignLeading("8", trailing: "-8", toView: middleView) - - if let style = datasource?.placeholderStyle() { - descriptionLabel?.implementStyle(style.description) - descriptionLabelHeight = descriptionLabel?.constrainHeight("\(UILabel.heightForLabelWithText(desc, style: style.description, width: middleView.bounds.width - 16))") - } else { - descriptionLabelHeight = descriptionLabel?.constrainHeight("30") - } - - //TODO: Add description style implementation here - } - - fileprivate func addButton(_ buttonTitle: String) { - - button = UIButton(type: .system) - button?.frame = CGRect.zero - button?.setTitle(buttonTitle, for: UIControlState()) - button?.addTarget(self, action: #selector(PlaceholderView.didPressButton), for: UIControlEvents.touchUpInside) - - if let style = datasource?.placeholderStyle() { - if style.button.borderType != .none { - button?.setTitle(" \(buttonTitle) ", for: UIControlState()) - } - button?.implementStyle(style.button) - } - - middleView.addSubview(button!) - middleView.bringSubview(toFront: button!) - setUpVerticalConstraints(button!) - _ = button?.alignCenterX(withView: middleView, predicate: "0") - buttonHeight = button?.constrainHeight("30") - } - - @objc func didPressButton() { - delegate?.placeholderButtonDidPress?() - } - - fileprivate func update() { - subviews.forEach({$0.removeFromSuperview()}) - if subviews.count != 0 { - print("subviews count != 0") - } - removeConstraints(constraints) - - bottomElement = nil - - addMiddleView() - - if let image = datasource?.placeholderImage() { - addImage(image) - middleViewHeight.constant += imageViewHeight?.constant ?? 0 - } else { - imageView = nil - } - - if let title = datasource?.placeholderTitle() { - addTitle(title) - middleViewHeight.constant += titleLabelHeight?.constant ?? 0 - } else { - titleLabel = nil - } - - if let desc = datasource?.placeholderDescription() { - addDescription(desc) - middleViewHeight.constant += descriptionLabelHeight?.constant ?? 0 - } else { - descriptionLabel = nil - } - - if let btitle = datasource?.placeholderButtonTitle() { - addButton(btitle) - middleViewHeight.constant += buttonHeight?.constant ?? 0 - } else { - button = nil - } - -// if let b = bottomElement { -// b.constrainBottomSpaceToView(middleView, predicate: "0") -// } else { -// print("No items in placeholder view") -// } - - middleView.layoutSubviews() - - print("middle view height -> \(middleView.bounds.height)") - print("middle view height -> \(middleView.bounds.height)") - setNeedsLayout() - layoutIfNeeded() - middleView.layoutSubviews() - invalidateIntrinsicContentSize() - print("middle view height -> \(middleView.bounds.height)") - print("image view height -> \(String(describing: imageView?.bounds.height))") - } - - fileprivate func setup() { - let middleView = UIView() - middleView.backgroundColor = UIColor.blue - self.addSubview(middleView) - } - - var delegate: PlaceholderViewDelegate? { - didSet { - update() - } - } - var datasource: PlaceholderViewDataSource? { - didSet { - update() - } - } - - override func layoutIfNeeded() { - print("middleView frame before -> \(middleView.frame)") - super.layoutIfNeeded() - print("middleView frame after -> \(middleView.frame)") - } - - override init(frame: CGRect) { - // 1. setup any properties here - - // 2. call super.init(frame:) - super.init(frame: frame) - setup() - } - - required init?(coder aDecoder: NSCoder) { - // 1. setup any properties here - - // 2. call super.init(coder:) - super.init(coder: aDecoder) - - // 3. Setup view from .xib file - setup() - } - - override var intrinsicContentSize: CGSize { - return CGSize(width: UIViewNoIntrinsicMetric, height: 250) - } - -} diff --git a/Stepic/PlaceholderViewDataSource.swift b/Stepic/PlaceholderViewDataSource.swift deleted file mode 100644 index fb367b8707..0000000000 --- a/Stepic/PlaceholderViewDataSource.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// PlaceholderViewDataSource.swift -// OstrenkiyPlaceholderView -// -// Created by Alexander Karpov on 02.02.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import UIKit - -protocol PlaceholderViewDataSource { - func placeholderImage() -> UIImage? - func placeholderStyle() -> PlaceholderStyle - func placeholderTitle() -> String? - func placeholderDescription() -> String? - func placeholderButtonTitle() -> String? -} diff --git a/Stepic/PlaceholderViewDelegate.swift b/Stepic/PlaceholderViewDelegate.swift deleted file mode 100644 index 2005b925a1..0000000000 --- a/Stepic/PlaceholderViewDelegate.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// PlaceholderViewDelegate.swift -// OstrenkiyPlaceholderView -// -// Created by Alexander Karpov on 02.02.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import UIKit - -@objc(PlaceholderViewDelegate) -protocol PlaceholderViewDelegate { - @objc optional func placeholderButtonDidPress() -} diff --git a/Stepic/ProfileViewController.swift b/Stepic/ProfileViewController.swift index cd98d00d6a..20319b4aa9 100644 --- a/Stepic/ProfileViewController.swift +++ b/Stepic/ProfileViewController.swift @@ -8,25 +8,51 @@ import UIKit import Presentr -import DZNEmptyDataSet -class ProfileViewController: MenuViewController, ProfileView { +class ProfileViewController: MenuViewController, ProfileView, ControllerWithStepikPlaceholder { + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() var presenter: ProfilePresenter? var shareBarButtonItem: UIBarButtonItem? - var state: ProfileState = .refreshing + var state: ProfileState = .refreshing { + didSet { + switch state { + case .refreshing: + showPlaceholder(for: .refreshing) + case .anonymous: + showPlaceholder(for: .anonymous) + case .error: + showPlaceholder(for: .connectionError) + case .authorized: + isPlaceholderShown = false + } + } + } override func viewDidLoad() { super.viewDidLoad() + registerPlaceholder(placeholder: StepikPlaceholder(.login, action: { [weak self] in + guard let strongSelf = self else { + return + } + + RoutingManager.auth.routeFrom(controller: strongSelf, success: nil, cancel: nil) + }), for: .anonymous) + + registerPlaceholder(placeholder: StepikPlaceholder(.noConnection, action: { [weak self] in + self?.presenter?.updateProfile() + }), for: .connectionError) + + registerPlaceholder(placeholder: StepikPlaceholder(.emptyProfileLoading), for: .refreshing) + + state = .refreshing + presenter = ProfilePresenter(view: self, userActivitiesAPI: ApiDataDownloader.userActivities, usersAPI: ApiDataDownloader.users, notificationPermissionManager: NotificationPermissionManager()) shareBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.action, target: self, action: #selector(ProfileViewController.shareButtonPressed)) self.navigationItem.rightBarButtonItem = shareBarButtonItem! - tableView.emptyDataSetSource = self - tableView.emptyDataSetDelegate = self - if #available(iOS 11.0, *) { tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentBehavior.never } @@ -204,113 +230,3 @@ class ProfileViewController: MenuViewController, ProfileView { streaksTooltip?.dismiss() } } - -extension ProfileViewController : DZNEmptyDataSetDelegate { - @objc func emptyDataSetShouldAllowScroll(_ scrollView: UIScrollView!) -> Bool { - return false - } - - @objc func emptyDataSetDidTapButton(_ scrollView: UIScrollView!) { - switch state { - case .anonymous: - RoutingManager.auth.routeFrom(controller: self, success: nil, cancel: nil) - break - case .error: - presenter?.updateProfile() - break - default: - break - } - } -} - -extension ProfileViewController : DZNEmptyDataSetSource { - func image(forEmptyDataSet scrollView: UIScrollView!) -> UIImage! { - switch state { - case .anonymous: - return Images.placeholders.anonymous - case .error: - return Images.placeholders.connectionError - case .refreshing: - return Images.placeholders.anonymous - default: - return UIImage() - } - } - - func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - - switch state { - case .anonymous: - text = NSLocalizedString("ProfileAnonymousTitle", comment: "") - case .error: - text = NSLocalizedString("ConnectionErrorTitle", comment: "") - break - case .refreshing: - text = NSLocalizedString("Refreshing", comment: "") - break - default: - break - } - - let attributes = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 18.0), - NSAttributedStringKey.foregroundColor: UIColor.darkGray] - - return NSAttributedString(string: text, attributes: attributes) - } - - func description(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - - switch state { - case .anonymous: - text = NSLocalizedString("ProfileAnonymousSubtitle", comment: "") - break - case .error: - text = "" - break - case .refreshing: - text = NSLocalizedString("RefreshingDescription", comment: "") - break - default: - break - } - - let paragraph = NSMutableParagraphStyle() - paragraph.lineBreakMode = .byWordWrapping - paragraph.alignment = .center - - let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14.0), - NSAttributedStringKey.foregroundColor: UIColor.lightGray, - NSAttributedStringKey.paragraphStyle: paragraph] - - return NSAttributedString(string: text, attributes: attributes) - } - - func buttonTitle(forEmptyDataSet scrollView: UIScrollView!, for state: UIControlState) -> NSAttributedString! { - var text: String = "" - - switch self.state { - case .anonymous: - text = NSLocalizedString("SignIn", comment: "") - case .error: - text = NSLocalizedString("TryAgain", comment: "") - break - case .refreshing: - text = "" - break - default: - break - } - - let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16.0), - NSAttributedStringKey.foregroundColor: UIColor.mainDark] - - return NSAttributedString(string: text, attributes: attributes) - } - - func backgroundColor(forEmptyDataSet scrollView: UIScrollView!) -> UIColor! { - return UIColor.groupTableViewBackground - } -} diff --git a/Stepic/QuizViewController.swift b/Stepic/QuizViewController.swift index 0beb6dcc07..f3d4009e9a 100644 --- a/Stepic/QuizViewController.swift +++ b/Stepic/QuizViewController.swift @@ -9,7 +9,8 @@ import UIKit import Presentr -class QuizViewController: UIViewController, QuizView, QuizControllerDataSource { +class QuizViewController: UIViewController, QuizView, QuizControllerDataSource, ControllerWithStepikPlaceholder { + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() @IBOutlet weak var sendButtonHeight: NSLayoutConstraint! @IBOutlet weak var sendButton: UIButton! @@ -50,21 +51,6 @@ class QuizViewController: UIViewController, QuizView, QuizControllerDataSource { } private let wrongTitle: String = NSLocalizedString("Wrong", comment: "") private let peerReviewText: String = NSLocalizedString("PeerReviewText", comment: "") - fileprivate let warningViewTitle = NSLocalizedString("ConnectionErrorText", comment: "") - - private var warningView: UIView? - private func initWarningView() -> UIView { - //TODO: change warning image! - let v = PlaceholderView() - self.view.insertSubview(v, aboveSubview: self.view) - v.align(toView: self.view) - v.constrainHeight("150") - v.delegate = self - v.datasource = self - v.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1000.0), for: UILayoutConstraintAxis.vertical) - v.backgroundColor = UIColor.white - return v - } private var activityView: UIView? private func initActivityView() -> UIView { @@ -106,20 +92,10 @@ class QuizViewController: UIViewController, QuizView, QuizControllerDataSource { private var doesPresentWarningView: Bool = false { didSet { if doesPresentWarningView { - DispatchQueue.main.async { - [weak self] in - if self?.warningView == nil { - self?.warningView = self?.initWarningView() - } - self?.warningView?.isHidden = false - } + showPlaceholder(for: .connectionError) self.presenter?.delegate?.didWarningPlaceholderShow() } else { - DispatchQueue.main.async { - [weak self] in - self?.warningView?.removeFromSuperview() - self?.warningView = nil - } + isPlaceholderShown = false } } } @@ -139,6 +115,10 @@ class QuizViewController: UIViewController, QuizView, QuizControllerDataSource { override func viewDidLoad() { super.viewDidLoad() + registerPlaceholder(placeholder: StepikPlaceholder(.noConnectionQuiz, action: { [weak self] in + self?.presenter?.refreshAttempt() + }), for: .connectionError) + self.hintView.setRoundedCorners(cornerRadius: 8, borderWidth: 1, borderColor: UIColor.black) self.hintHeightWebViewHelper = CellWebViewHelper(webView: hintWebView) self.hintView.backgroundColor = UIColor.black @@ -464,34 +444,6 @@ class QuizViewController: UIViewController, QuizView, QuizControllerDataSource { } } -extension QuizViewController : PlaceholderViewDataSource { - func placeholderImage() -> UIImage? { - return Images.noWifiImage.size100x100 - } - - func placeholderButtonTitle() -> String? { - return NSLocalizedString("TryAgain", comment: "") - } - - func placeholderDescription() -> String? { - return nil - } - - func placeholderStyle() -> PlaceholderStyle { - return stepicPlaceholderStyle - } - - func placeholderTitle() -> String? { - return warningViewTitle - } -} - -extension QuizViewController : PlaceholderViewDelegate { - func placeholderButtonDidPress() { - self.presenter?.refreshAttempt() - } -} - extension QuizViewController : UIWebViewDelegate { func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool { if let url = request.url { diff --git a/Stepic/SectionsViewController.swift b/Stepic/SectionsViewController.swift index 40e2964702..a33f3c5f7e 100644 --- a/Stepic/SectionsViewController.swift +++ b/Stepic/SectionsViewController.swift @@ -8,11 +8,11 @@ import UIKit import DownloadButton -import DZNEmptyDataSet -class SectionsViewController: UIViewController, ShareableController, UIViewControllerPreviewingDelegate { +class SectionsViewController: UIViewController, ShareableController, UIViewControllerPreviewingDelegate, ControllerWithStepikPlaceholder { + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() - @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var tableView: StepikTableView! let refreshControl = UIRefreshControl() var didRefresh = false @@ -29,6 +29,8 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr override func viewDidLoad() { super.viewDidLoad() + registerPlaceholder(placeholder: StepikPlaceholder(.noConnection), for: .connectionError) + LastStepGlobalContext.context.course = course self.navigationItem.title = course.title @@ -42,6 +44,8 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr self.navigationItem.rightBarButtonItems = [shareBarButtonItem, infoBarButtonItem] tableView.register(UINib(nibName: "SectionTableViewCell", bundle: nil), forCellReuseIdentifier: "SectionTableViewCell") + tableView.emptySetPlaceholder = StepikPlaceholder(.emptySections) + tableView.loadingPlaceholder = StepikPlaceholder(.emptySectionsLoading) refreshControl.addTarget(self, action: #selector(SectionsViewController.refreshSections), for: .valueChanged) if #available(iOS 10.0, *) { @@ -53,9 +57,6 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr refreshControl.beginRefreshing() refreshSections() - tableView.emptyDataSetDelegate = self - tableView.emptyDataSetSource = self - tableView.estimatedRowHeight = 44.0 tableView.rowHeight = UITableViewAutomaticDimension @@ -155,8 +156,15 @@ class SectionsViewController: UIViewController, ShareableController, UIViewContr var emptyDatasetState: EmptyDatasetState = .empty { didSet { - UIThread.performUI { - self.tableView.reloadEmptyDataSet() + switch emptyDatasetState { + case .refreshing: + isPlaceholderShown = false + tableView.showLoadingPlaceholder() + case .empty: + isPlaceholderShown = false + tableView.reloadData() + case .connectionError: + showPlaceholder(for: .connectionError) } } } @@ -467,78 +475,3 @@ extension SectionsViewController : PKDownloadButtonDelegate { } } } - -extension SectionsViewController : DZNEmptyDataSetSource { - - func image(forEmptyDataSet scrollView: UIScrollView!) -> UIImage! { - switch emptyDatasetState { - case .empty: - return Images.emptyCoursesPlaceholder - case .connectionError: - return Images.noWifiImage.size250x250 - case .refreshing: - return Images.emptyCoursesPlaceholder - } - } - - func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - switch emptyDatasetState { - case .empty: - text = NSLocalizedString("EmptyTitle", comment: "") - break - case .connectionError: - text = NSLocalizedString("ConnectionErrorTitle", comment: "") - break - case .refreshing: - text = NSLocalizedString("Refreshing", comment: "") - break - } - - let attributes = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 18.0), - NSAttributedStringKey.foregroundColor: UIColor.darkGray] - - return NSAttributedString(string: text, attributes: attributes) - } - - func description(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - - switch emptyDatasetState { - case .empty: - text = NSLocalizedString("PullToRefreshSectionsDescription", comment: "") - break - case .connectionError: - text = NSLocalizedString("PullToRefreshSectionsDescription", comment: "") - break - case .refreshing: - text = NSLocalizedString("RefreshingDescription", comment: "") - break - } - - let paragraph = NSMutableParagraphStyle() - paragraph.lineBreakMode = .byWordWrapping - paragraph.alignment = .center - - let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14.0), - NSAttributedStringKey.foregroundColor: UIColor.lightGray, - NSAttributedStringKey.paragraphStyle: paragraph] - - return NSAttributedString(string: text, attributes: attributes) - } - - func backgroundColor(forEmptyDataSet scrollView: UIScrollView!) -> UIColor! { - return UIColor.white - } - - func verticalOffset(forEmptyDataSet scrollView: UIScrollView!) -> CGFloat { - // print("offset -> \((self.navigationController?.navigationBar.bounds.height) ?? 0 + UIApplication.sharedApplication().statusBarFrame.height)") - return 44 - } -} - -extension SectionsViewController : DZNEmptyDataSetDelegate { - func emptyDataSetShouldAllowScroll(_ scrollView: UIScrollView!) -> Bool { - return true - } -} diff --git a/Stepic/Stepic-Bridging-Header.h b/Stepic/Stepic-Bridging-Header.h index a989530adc..43a49a0abb 100644 --- a/Stepic/Stepic-Bridging-Header.h +++ b/Stepic/Stepic-Bridging-Header.h @@ -8,6 +8,4 @@ #import -#import - #import "WKWebViewPanelManager.h" diff --git a/Stepic/StepikPlaceholder.swift b/Stepic/StepikPlaceholder.swift index 7c28696f61..328156ea11 100644 --- a/Stepic/StepikPlaceholder.swift +++ b/Stepic/StepikPlaceholder.swift @@ -11,14 +11,10 @@ import UIKit typealias StepikPlaceholderStyle = StepikPlaceholder.Style class StepikPlaceholder { - class var availablePlaceholders: [StepikPlaceholderStyle] { - return [Style.empty, Style.noConnection, Style.login, Style.emptyDownloads, Style.emptyNotifications, Style.emptySearch] - } - var style: StepikPlaceholderStyle var buttonAction: (() -> Void)? - init(_ style: StepikPlaceholderStyle, action: (() -> Void)?) { + init(_ style: StepikPlaceholderStyle, action: (() -> Void)? = nil) { self.style = style self.buttonAction = action } @@ -44,45 +40,3 @@ class StepikPlaceholder { } } } - -extension StepikPlaceholder.Style { - static let empty = StepikPlaceholderStyle(id: "empty", - image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), - text: NSLocalizedString("PlaceholderEmptyText", comment: ""), - buttonTitle: NSLocalizedString("PlaceholderEmptyButton", comment: "")) - static let noConnection = StepikPlaceholderStyle(id: "noConnection", - image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-noconnection"), scale: 0.35), - text: NSLocalizedString("PlaceholderNoConnectionText", comment: ""), - buttonTitle: NSLocalizedString("PlaceholderNoConnectionButton", comment: "")) - static let emptyDownloads = StepikPlaceholderStyle(id: "emptyDownloads", - image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-downloads"), scale: 0.46), - text: NSLocalizedString("PlaceholderEmptyDownloadsText", comment: ""), - buttonTitle: NSLocalizedString("PlaceholderEmptyDownloadsButton", comment: "")) - static let login = StepikPlaceholderStyle(id: "login", - image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-login"), scale: 0.59), - text: NSLocalizedString("PlaceholderLoginText", comment: ""), - buttonTitle: NSLocalizedString("PlaceholderLoginButton", comment: "")) - static let emptyNotifications = StepikPlaceholderStyle(id: "emptyNotifications", - image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-notifications"), scale: 0.48), - text: NSLocalizedString("PlaceholderEmptyNotificationsText", comment: ""), - buttonTitle: NSLocalizedString("PlaceholderEmptyNotificationsButton", comment: "")) - static let emptySearch = StepikPlaceholderStyle(id: "emptySearch", - image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-search"), scale: 0.49), - text: NSLocalizedString("PlaceholderEmptySearchText", comment: ""), - buttonTitle: NSLocalizedString("PlaceholderEmptySearchButton", comment: "")) -} - -class StepikPlaceholderContainer { - private var placeholder: StepikPlaceholder - - init(_ placeholder: StepikPlaceholder) { - self.placeholder = placeholder - } - - func build() -> StepikPlaceholderView { - let view = StepikPlaceholderView() - view.set(id: placeholder.style.id) - - return view - } -} diff --git a/Stepic/StepikPlaceholderStyle+Placeholders.swift b/Stepic/StepikPlaceholderStyle+Placeholders.swift new file mode 100644 index 0000000000..1a3744ebb0 --- /dev/null +++ b/Stepic/StepikPlaceholderStyle+Placeholders.swift @@ -0,0 +1,90 @@ +// +// StepikPlaceholderStyle+Placeholders.swift +// Stepic +// +// Created by Vladislav Kiryukhin on 20.03.2018. +// Copyright © 2018 Alex Karpov. All rights reserved. +// + +import Foundation + +extension StepikPlaceholder.Style { + static let empty = StepikPlaceholderStyle(id: "empty", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("PlaceholderEmptyText", comment: ""), + buttonTitle: NSLocalizedString("PlaceholderEmptyButton", comment: "")) + static let noConnection = StepikPlaceholderStyle(id: "noConnection", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-noconnection"), scale: 0.35), + text: NSLocalizedString("PlaceholderNoConnectionText", comment: ""), + buttonTitle: NSLocalizedString("PlaceholderNoConnectionButton", comment: "")) + static let emptyDownloads = StepikPlaceholderStyle(id: "emptyDownloads", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-downloads"), scale: 0.46), + text: NSLocalizedString("PlaceholderEmptyDownloadsText", comment: ""), + buttonTitle: NSLocalizedString("PlaceholderEmptyDownloadsButton", comment: "")) + static let login = StepikPlaceholderStyle(id: "login", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-login"), scale: 0.59), + text: NSLocalizedString("PlaceholderLoginText", comment: ""), + buttonTitle: NSLocalizedString("PlaceholderLoginButton", comment: "")) + static let emptyNotifications = StepikPlaceholderStyle(id: "emptyNotifications", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-notifications"), scale: 0.48), + text: NSLocalizedString("PlaceholderEmptyNotificationsText", comment: ""), + buttonTitle: NSLocalizedString("PlaceholderEmptyNotificationsButton", comment: "")) + static let emptyNotificationsLoading = StepikPlaceholderStyle(id: "emptyNotificationsLoading", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-notifications"), scale: 0.48), + text: NSLocalizedString("Refreshing", comment: ""), + buttonTitle: nil) + static let emptySearch = StepikPlaceholderStyle(id: "emptySearch", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-search"), scale: 0.49), + text: NSLocalizedString("PlaceholderEmptySearchText", comment: ""), + buttonTitle: NSLocalizedString("PlaceholderEmptySearchButton", comment: "")) + static let emptyCertificates = StepikPlaceholderStyle(id: "emptyCertificates", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("EmptyCertificatesTitle", comment: ""), + buttonTitle: NSLocalizedString("ChooseCourse", comment: "")) + static let emptyCertificatesLoading = StepikPlaceholderStyle(id: "emptyCertificatesLoading", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("Refreshing", comment: ""), + buttonTitle: nil) + static let emptyDiscussions = StepikPlaceholderStyle(id: "emptyDiscussions", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("NoDiscussionsTitle", comment: ""), + buttonTitle: nil) + static let emptyDiscussionsLoading = StepikPlaceholderStyle(id: "emptyDiscussionsLoading", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("Refreshing", comment: ""), + buttonTitle: nil) + static let emptyProfileLoading = StepikPlaceholderStyle(id: "emptyProfileLoading", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("Refreshing", comment: ""), + buttonTitle: nil) + static let emptySections = StepikPlaceholderStyle(id: "emptySections", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("EmptyTitle", comment: ""), + buttonTitle: nil) + static let emptySectionsLoading = StepikPlaceholderStyle(id: "emptySectionsLoading", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("Refreshing", comment: ""), + buttonTitle: nil) + static let emptyUnits = StepikPlaceholderStyle(id: "emptyUnits", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("PullToRefreshUnitsTitle", comment: ""), + buttonTitle: nil) + static let emptyUnitsLoading = StepikPlaceholderStyle(id: "emptyUnitsLoading", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("Refreshing", comment: ""), + buttonTitle: nil) + static let noConnectionQuiz = StepikPlaceholderStyle(id: "noConnectionQuiz", + image: nil, + text: NSLocalizedString("ConnectionErrorText", comment: ""), + buttonTitle: NSLocalizedString("TryAgain", comment: "")) + static let adaptiveCoursePassed = StepikPlaceholderStyle(id: "adaptiveCoursePassed", + image: PlaceholderImage(image: #imageLiteral(resourceName: "new-empty-empty"), scale: 0.99), + text: NSLocalizedString("NoRecommendations", comment: ""), + buttonTitle: nil) +} + +extension StepikPlaceholder.Style { + class var stepikStyledPlaceholders: [StepikPlaceholderStyle] { + return [StepikPlaceholderStyle.empty, StepikPlaceholderStyle.noConnection, StepikPlaceholderStyle.login, StepikPlaceholderStyle.emptyDownloads, StepikPlaceholderStyle.emptyNotifications, StepikPlaceholderStyle.emptySearch, StepikPlaceholderStyle.emptyCertificates, StepikPlaceholderStyle.emptyDiscussions, StepikPlaceholderStyle.emptyProfileLoading, StepikPlaceholderStyle.emptySections, StepikPlaceholderStyle.emptyUnits] + } +} diff --git a/Stepic/StepikPlaceholderView.swift b/Stepic/StepikPlaceholderView.swift index ad1fcf5a9e..11080d504a 100644 --- a/Stepic/StepikPlaceholderView.swift +++ b/Stepic/StepikPlaceholderView.swift @@ -34,7 +34,7 @@ class StepikPlaceholderView: NibInitializableView { lazy private var allPlaceholders: [StepikPlaceholderStyle.PlaceholderId: StepikPlaceholderStyle] = { var idToView: [StepikPlaceholderStyle.PlaceholderId: StepikPlaceholderStyle] = [:] - for placeholder in StepikPlaceholder.availablePlaceholders { + for placeholder in StepikPlaceholderStyle.stepikStyledPlaceholders { idToView[placeholder.id] = placeholder } return idToView @@ -48,6 +48,11 @@ class StepikPlaceholderView: NibInitializableView { return "StepikPlaceholderView" } + convenience init(placeholder: StepikPlaceholderStyle) { + self.init() + set(placeholder: placeholder) + } + @IBAction func onActionButtonClick(_ sender: Any) { delegate?.buttonDidClick(actionButton) } @@ -96,13 +101,11 @@ class StepikPlaceholderView: NibInitializableView { } if placeholder.buttonTitle != nil { - if !actionsStackView.arrangedSubviews.contains(actionButton) { - actionsStackView.insertArrangedSubview(actionButton, at: 1) - } + actionButton.alpha = 1.0 actionButton.isHidden = false } else { - actionsStackView.removeArrangedSubview(actionButton) - actionButton.isHidden = true + actionButton.alpha = 0.0 + actionButton.isHidden = !isVertical } if isVertical { @@ -123,17 +126,14 @@ class StepikPlaceholderView: NibInitializableView { } } - func set(id: StepikPlaceholderStyle.PlaceholderId) { - guard let placeholderWithId = allPlaceholders[id] else { - return - } + func set(placeholder: StepikPlaceholderStyle) { + currentPlaceholder = placeholder - currentPlaceholder = placeholderWithId + imageView.image = placeholder.image?.image - imageView.image = placeholderWithId.image?.image - textLabel.text = placeholderWithId.text - actionButton.setTitle(placeholderWithId.buttonTitle, for: .normal) + textLabel.text = placeholder.text + actionButton.setTitle(placeholder.buttonTitle, for: .normal) - rebuildConstraints(for: placeholderWithId) + rebuildConstraints(for: placeholder) } } diff --git a/Stepic/StepikTableView.swift b/Stepic/StepikTableView.swift index 966e79d6cb..89f3e3623e 100644 --- a/Stepic/StepikTableView.swift +++ b/Stepic/StepikTableView.swift @@ -11,18 +11,16 @@ import UIKit class StepikTableView: UITableView { // Empty state placeholder - var emptySetPlaceholder: StepikPlaceholder? { - didSet { - if let p = emptySetPlaceholder { - emptySetView?.removeFromSuperview() - emptySetView = StepikPlaceholderContainer(p).build() - (emptySetView as? StepikPlaceholderView)?.delegate = self - } - } - } + var emptySetPlaceholder: StepikPlaceholder? - // View for empty state - private var emptySetView: UIView? + // Loading state placeholder + var loadingPlaceholder: StepikPlaceholder? + + // View for placeholders + lazy private var placeholderView: StepikPlaceholderView = { + let view = StepikPlaceholderView() + return view + }() // Trick with removing cell separators: we should store previous footer to restore private var savedFooterView: UIView? @@ -33,48 +31,59 @@ extension StepikTableView { return (0.. 0 } - private func handleEmptySetView(isHidden: Bool) { + private func handlePlaceholder(isHidden: Bool) { if isHidden { tableFooterView = savedFooterView - emptySetView?.isHidden = true + placeholderView.isHidden = true return } - updateEmptySetLayout() + updatePlaceholderLayout() // Remove cell separators savedFooterView = self.tableFooterView tableFooterView = UIView() + placeholderView.isHidden = false + } - emptySetView?.isHidden = false + private func handleEmptySetPlaceholder(isHidden: Bool) { + if let p = emptySetPlaceholder, !isHidden { + placeholderView.set(placeholder: p.style) + placeholderView.delegate = self + } + handlePlaceholder(isHidden: isHidden) } - private func updateEmptySetLayout() { - guard let emptySetView = emptySetView else { - return + func showLoadingPlaceholder(force: Bool = false) { + if let p = loadingPlaceholder { + placeholderView.set(placeholder: p.style) + placeholderView.delegate = self } + handlePlaceholder(isHidden: !force && hasContent) + } - if emptySetView.superview == nil { - emptySetView.translatesAutoresizingMaskIntoConstraints = false + private func updatePlaceholderLayout() { + if placeholderView.superview == nil { + placeholderView.translatesAutoresizingMaskIntoConstraints = false - addSubview(emptySetView) - emptySetView.alignCenter(withView: self) - emptySetView.align(toView: self) + addSubview(placeholderView) + placeholderView.alignCenter(withView: self) + placeholderView.align(toView: self) - emptySetView.setNeedsLayout() - emptySetView.layoutIfNeeded() + placeholderView.setNeedsLayout() + placeholderView.layoutIfNeeded() } - bringSubview(toFront: emptySetView) + bringSubview(toFront: placeholderView) } override func reloadData() { super.reloadData() - handleEmptySetView(isHidden: hasContent) + handleEmptySetPlaceholder(isHidden: hasContent) } override func endUpdates() { super.endUpdates() - handleEmptySetView(isHidden: hasContent) + handleEmptySetPlaceholder(isHidden: hasContent) } } diff --git a/Stepic/Styles.swift b/Stepic/Styles.swift deleted file mode 100644 index a5ccd503f3..0000000000 --- a/Stepic/Styles.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Styles.swift -// OstrenkiyPlaceholderView -// -// Created by Alexander Karpov on 08.02.16. -// Copyright © 2016 Alex Karpov. All rights reserved. -// - -import UIKit - -struct PlaceholderStyle { - struct LabelStyle { - var font: UIFont = UIFont.systemFont(ofSize: 14) - var textColor: UIColor = UIColor.lightGray - var textAlignment: NSTextAlignment = NSTextAlignment.center - var lineBreakMode: NSLineBreakMode = NSLineBreakMode.byWordWrapping - } - - struct ButtonStyle { - var font: UIFont = UIFont.systemFont(ofSize: 17) - var borderType: BorderType = .none - var borderColor: UIColor = UIColor.clear - var backgroundColor: UIColor = UIColor.clear - var textColor: UIColor = UIColor.blue - } - - var title = LabelStyle() - var description = LabelStyle() - var button = ButtonStyle() -} - -var stepicPlaceholderStyle: PlaceholderStyle { - var style = PlaceholderStyle() - style.title.font = UIFont.boldSystemFont(ofSize: 18) - style.button.borderType = .none - style.button.borderColor = UIColor.mainDark - style.button.backgroundColor = UIColor.white - style.button.textColor = UIColor.mainDark - return style -} - -enum BorderType { - case none, rounded, rect -} diff --git a/Stepic/UnitsViewController.swift b/Stepic/UnitsViewController.swift index 24adeb2827..79c160d34a 100644 --- a/Stepic/UnitsViewController.swift +++ b/Stepic/UnitsViewController.swift @@ -8,11 +8,11 @@ import UIKit import DownloadButton -import DZNEmptyDataSet -class UnitsViewController: UIViewController, ShareableController, UIViewControllerPreviewingDelegate { +class UnitsViewController: UIViewController, ShareableController, UIViewControllerPreviewingDelegate, ControllerWithStepikPlaceholder { + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() - @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var tableView: StepikTableView! /* There are 2 ways of instantiating the controller @@ -36,6 +36,8 @@ class UnitsViewController: UIViewController, ShareableController, UIViewControll override func viewDidLoad() { super.viewDidLoad() + registerPlaceholder(placeholder: StepikPlaceholder(.noConnection), for: .connectionError) + updateTitle() self.navigationItem.backBarButtonItem?.title = " " @@ -54,8 +56,9 @@ class UnitsViewController: UIViewController, ShareableController, UIViewControll } refreshControl.layoutIfNeeded() - tableView.emptyDataSetDelegate = self - tableView.emptyDataSetSource = self + tableView.emptySetPlaceholder = StepikPlaceholder(.emptyUnits) + tableView.loadingPlaceholder = StepikPlaceholder(.emptyUnitsLoading) + refreshControl.beginRefreshing() refreshUnits() @@ -208,8 +211,15 @@ class UnitsViewController: UIViewController, ShareableController, UIViewControll var emptyDatasetState: EmptyDatasetState = .empty { didSet { - UIThread.performUI { - self.tableView.reloadEmptyDataSet() + switch emptyDatasetState { + case .refreshing: + isPlaceholderShown = false + tableView.showLoadingPlaceholder() + case .empty: + isPlaceholderShown = false + tableView.reloadData() + case .connectionError: + showPlaceholder(for: .connectionError) } } } @@ -663,78 +673,3 @@ extension UnitsViewController : PKDownloadButtonDelegate { } } } - -extension UnitsViewController : DZNEmptyDataSetSource { - - func image(forEmptyDataSet scrollView: UIScrollView!) -> UIImage! { - switch emptyDatasetState { - case .empty: - return Images.emptyCoursesPlaceholder - case .connectionError: - return Images.noWifiImage.size250x250 - case .refreshing: - return Images.emptyCoursesPlaceholder - } - } - - func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - switch emptyDatasetState { - case .empty: - text = NSLocalizedString("PullToRefreshUnitsTitle", comment: "") - break - case .connectionError: - text = NSLocalizedString("ConnectionErrorTitle", comment: "") - break - case .refreshing: - text = NSLocalizedString("Refreshing", comment: "") - break - } - - let attributes = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 18.0), - NSAttributedStringKey.foregroundColor: UIColor.darkGray] - - return NSAttributedString(string: text, attributes: attributes) - } - - func description(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! { - var text: String = "" - - switch emptyDatasetState { - case .empty: - text = NSLocalizedString("PullToRefreshUnitsDescription", comment: "") - break - case .connectionError: - text = NSLocalizedString("PullToRefreshUnitsDescription", comment: "") - break - case .refreshing: - text = NSLocalizedString("RefreshingDescription", comment: "") - break - } - - let paragraph = NSMutableParagraphStyle() - paragraph.lineBreakMode = .byWordWrapping - paragraph.alignment = .center - - let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14.0), - NSAttributedStringKey.foregroundColor: UIColor.lightGray, - NSAttributedStringKey.paragraphStyle: paragraph] - - return NSAttributedString(string: text, attributes: attributes) - } - - func backgroundColor(forEmptyDataSet scrollView: UIScrollView!) -> UIColor! { - return UIColor.white - } - - func verticalOffset(forEmptyDataSet scrollView: UIScrollView!) -> CGFloat { - // print("offset -> \((self.navigationController?.navigationBar.bounds.height) ?? 0 + UIApplication.sharedApplication().statusBarFrame.height)") - return 44 - } -} - -extension UnitsViewController : DZNEmptyDataSetDelegate { - func emptyDataSetShouldAllowScroll(_ scrollView: UIScrollView!) -> Bool { - return true - } -} diff --git a/StepicAdaptiveCourse/AdaptiveCardsStepsViewController.swift b/StepicAdaptiveCourse/AdaptiveCardsStepsViewController.swift index 00292e3f60..8bc63eae11 100644 --- a/StepicAdaptiveCourse/AdaptiveCardsStepsViewController.swift +++ b/StepicAdaptiveCourse/AdaptiveCardsStepsViewController.swift @@ -8,6 +8,13 @@ import UIKit +extension StepikPlaceholder.Style { + static let adaptiveCoursePassedAdaptive = StepikPlaceholderStyle(id: "adaptiveCoursePassedAdaptive", + image: nil, + text: NSLocalizedString("NoRecommendations", comment: ""), + buttonTitle: nil) +} + class AdaptiveCardsStepsViewController: CardsStepsViewController { @IBOutlet weak var levelProgress: RatingProgressView! @IBOutlet weak var tapProxyView: TapProxyView! @@ -48,6 +55,8 @@ class AdaptiveCardsStepsViewController: CardsStepsViewController { override func viewDidLoad() { super.viewDidLoad() + registerPlaceholder(placeholder: StepikPlaceholder(.adaptiveCoursePassedAdaptive), for: .adaptiveCoursePassed) + tapProxyView.targetView = trophyButton tapBackProxyView.targetView = backButton trophyButton.tintColor = UIColor.mainDark diff --git a/StepicAdaptiveCourse/AdaptiveCourseSelectViewController.swift b/StepicAdaptiveCourse/AdaptiveCourseSelectViewController.swift index de740300ee..3f1c0e7dd2 100644 --- a/StepicAdaptiveCourse/AdaptiveCourseSelectViewController.swift +++ b/StepicAdaptiveCourse/AdaptiveCourseSelectViewController.swift @@ -12,7 +12,15 @@ enum AdaptiveCourseSelectViewState { case loading, normal, error } -class AdaptiveCourseSelectViewController: UIViewController, AdaptiveCourseSelectView { +extension StepikPlaceholder.Style { + static let noConnectionAdaptive = StepikPlaceholderStyle(id: "noConnectionAdaptive", + image: nil, + text: NSLocalizedString("PlaceholderNoConnectionText", comment: ""), + buttonTitle: NSLocalizedString("PlaceholderNoConnectionButton", comment: "")) +} + +class AdaptiveCourseSelectViewController: UIViewController, AdaptiveCourseSelectView, ControllerWithStepikPlaceholder { + var placeholderContainer: StepikPlaceholderControllerContainer = StepikPlaceholderControllerContainer() @IBOutlet weak var tableView: UITableView! @IBOutlet weak var loadingContainerView: UIView! @@ -21,32 +29,20 @@ class AdaptiveCourseSelectViewController: UIViewController, AdaptiveCourseSelect var data: [AdaptiveCourseSelectViewData] = [] var didControllerDisplayBefore = false - lazy var placeholderView: PlaceholderView = { - let v = PlaceholderView() - self.view.insertSubview(v, aboveSubview: self.view) - v.align(toView: self.view) - v.delegate = self - v.backgroundColor = self.view.backgroundColor - return v - }() - var presenter: AdaptiveCourseSelectPresenter? var state: AdaptiveCourseSelectViewState = .normal { didSet { switch state { case .normal: - self.placeholderView.isHidden = true + isPlaceholderShown = false self.tableView.isHidden = false self.loadingContainerView.isHidden = true case .error: - self.placeholderView.isHidden = false + showPlaceholder(for: .connectionError) self.tableView.isHidden = true self.loadingContainerView.isHidden = true - - // Refresh placeholder state - self.placeholderView.datasource = self case .loading: - self.placeholderView.isHidden = true + isPlaceholderShown = false self.tableView.isHidden = true self.loadingContainerView.isHidden = false } @@ -56,6 +52,10 @@ class AdaptiveCourseSelectViewController: UIViewController, AdaptiveCourseSelect override func viewDidLoad() { super.viewDidLoad() + registerPlaceholder(placeholder: StepikPlaceholder(.noConnectionAdaptive, action: { [weak self] in + self?.presenter?.tryAgain() + }), for: .connectionError) + loadingLabel.text = NSLocalizedString("AdaptiveCourseSelectLoading", comment: "") title = NSLocalizedString("AdaptiveCourseSelectTitle", comment: "") @@ -130,53 +130,3 @@ extension AdaptiveCourseSelectViewController: AdaptiveCourseTableViewCellDelegat tableView(tableView, didSelectRowAt: indexPath) } } - -extension AdaptiveCourseSelectViewController: PlaceholderViewDataSource { - func placeholderImage() -> UIImage? { - switch state { - case .error: - return Images.placeholders.connectionError - default: - return nil - } - } - - func placeholderButtonTitle() -> String? { - switch state { - case .error: - return NSLocalizedString("TryAgain", comment: "") - default: - return nil - } - } - - func placeholderDescription() -> String? { - switch state { - case .error: - return nil - default: - return nil - } - } - - func placeholderStyle() -> PlaceholderStyle { - var style = PlaceholderStyle() - style.button.textColor = UIColor.mainDark - return style - } - - func placeholderTitle() -> String? { - switch state { - case .error: - return NSLocalizedString("ConnectionErrorText", comment: "") - default: - return nil - } - } -} - -extension AdaptiveCourseSelectViewController: PlaceholderViewDelegate { - func placeholderButtonDidPress() { - presenter?.tryAgain() - } -}