diff --git a/LeadKit.podspec b/LeadKit.podspec index 59f27cf7..5908a6f1 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -40,17 +40,17 @@ Pod::Spec.new do |s| ss.source_files = "Sources/**/*.swift" ss.watchos.exclude_files = [ - "Sources/Classes/Pagination/PaginationTableViewWrapper.swift", "Sources/Classes/Views/SeparatorRowBox/*", "Sources/Classes/Views/SeparatorCell/*", "Sources/Classes/Views/EmptyCell/*", + "Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift", "Sources/Classes/Views/XibView/*", "Sources/Classes/Views/SpinnerView/*", "Sources/Extensions/CABasicAnimation/*", "Sources/Extensions/CGFloat/CGFloat+Pixels.swift", "Sources/Extensions/NetworkService/NetworkService+ActivityIndicator.swift", "Sources/Extensions/NetworkService/NetworkService+RxLoadImage.swift", - "Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift", + "Sources/Extensions/DataLoading/PaginationDataLoading/*", "Sources/Extensions/Support/UIScrollView+Support.swift", "Sources/Extensions/TableDirector/*", "Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift", @@ -59,36 +59,40 @@ Pod::Spec.new do |s| "Sources/Extensions/UICollectionView/*", "Sources/Extensions/UIDevice/*", "Sources/Extensions/UIImage/*", - "Sources/Extensions/UITableView/*", + "Sources/Extensions/UITableView/*", "Sources/Extensions/UIView/*", "Sources/Extensions/UIViewController/*", "Sources/Extensions/UIWindow/*", "Sources/Protocols/LoadingIndicator.swift", + "Sources/Protocols/DataLoading/PaginationDataLoading/PaginationWrappable.swift", "Sources/Structures/Views/AnyLoadingIndicator.swift", "Sources/Structures/DrawingOperations/CALayerDrawingOperation.swift", "Sources/Structures/DrawingOperations/RoundDrawingOperation.swift", "Sources/Structures/DrawingOperations/BorderDrawingOperation.swift", + "Sources/Structures/DataLoading/PaginationDataLoading/*" ] ss.tvos.exclude_files = [ "Sources/Classes/Views/SeparatorRowBox/*", "Sources/Classes/Views/SeparatorCell/*", "Sources/Classes/Views/EmptyCell/*", - "Sources/Classes/Pagination/PaginationTableViewWrapper.swift", + "Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift", "Sources/Structures/Drawing/CALayerDrawingOperation.swift", "Sources/Extensions/NetworkService/NetworkService+ActivityIndicator.swift", - "Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift", + "Sources/Extensions/DataLoading/PaginationDataLoading/*", "Sources/Extensions/Support/UIScrollView+Support.swift", "Sources/Extensions/TableDirector/*", - "Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift" + "Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift", + "Sources/Protocols/DataLoading/PaginationDataLoading/PaginationWrappable.swift", + "Sources/Structures/DataLoading/PaginationDataLoading/*" ] - ss.dependency "CocoaLumberjack/Swift", '~> 3.3.0' - ss.dependency "RxSwift", '4.0.0' - ss.dependency "RxCocoa", '4.0.0' - ss.dependency "RxAlamofire", '4.0.0' - ss.dependency "ObjectMapper", '~> 3.0.0' + ss.dependency "CocoaLumberjack/Swift", '~> 3.4' + ss.dependency "RxSwift", '~> 4.1' + ss.dependency "RxCocoa", '~> 4.1' + ss.dependency "RxAlamofire", '~> 4.1' + ss.dependency "ObjectMapper", '~> 3.0' - ss.ios.dependency "TableKit", '~> 2.5.0' + ss.ios.dependency "TableKit", '~> 2.6' ss.ios.dependency "UIScrollView-InfiniteScroll", '~> 1.0.0' end @@ -101,18 +105,18 @@ Pod::Spec.new do |s| "Sources/Classes/Views/SeparatorRowBox/*", "Sources/Classes/Views/SeparatorCell/*", "Sources/Classes/Views/EmptyCell/*", - "Sources/Classes/Pagination/PaginationTableViewWrapper.swift", + "Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift", "Sources/Extensions/NetworkService/NetworkService+ActivityIndicator.swift", - "Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift", + "Sources/Extensions/DataLoading/PaginationDataLoading/*", "Sources/Extensions/TableDirector/*", "Sources/Extensions/Array/Array+SeparatorRowBoxExtensions.swift" ] - ss.dependency "CocoaLumberjack/Swift", '~> 3.3.0' - ss.dependency "RxSwift", '4.0.0' - ss.dependency "RxCocoa", '4.0.0' - ss.dependency "RxAlamofire", '4.0.0' - ss.dependency "ObjectMapper", '~> 3.0.0' + ss.dependency "CocoaLumberjack/Swift", '~> 3.4' + ss.dependency "RxSwift", '~> 4.1' + ss.dependency "RxCocoa", '~> 4.1' + ss.dependency "RxAlamofire", '~> 4.1' + ss.dependency "ObjectMapper", '~> 3.0' end s.default_subspec = 'Core' diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index 879d6d3a..0d830e52 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -41,11 +41,6 @@ 6714625D1EB3396E00EAB194 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461CE1EB3396E00EAB194 /* LogFormatter.swift */; }; 6714625E1EB3396E00EAB194 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461CE1EB3396E00EAB194 /* LogFormatter.swift */; }; 6714625F1EB3396E00EAB194 /* LogFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461CE1EB3396E00EAB194 /* LogFormatter.swift */; }; - 671462601EB3396E00EAB194 /* PaginationTableViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D01EB3396E00EAB194 /* PaginationTableViewWrapper.swift */; }; - 671462641EB3396E00EAB194 /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */; }; - 671462651EB3396E00EAB194 /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */; }; - 671462661EB3396E00EAB194 /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */; }; - 671462671EB3396E00EAB194 /* PaginationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */; }; 671462681EB3396E00EAB194 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D31EB3396E00EAB194 /* NetworkService.swift */; }; 671462691EB3396E00EAB194 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D31EB3396E00EAB194 /* NetworkService.swift */; }; 6714626A1EB3396E00EAB194 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461D31EB3396E00EAB194 /* NetworkService.swift */; }; @@ -108,7 +103,6 @@ 671462AD1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */; }; 671462AE1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */; }; 671462AF1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */; }; - 671462B41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift */; }; 671462B81EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */; }; 671462B91EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */; }; 671462BA1EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */; }; @@ -304,6 +298,57 @@ 6771DFEB1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6771DFE91EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift */; }; 6771DFEC1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6771DFE91EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift */; }; 6771DFED1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6771DFE91EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift */; }; + 67745268206249360024EEEF /* UITableView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745267206249360024EEEF /* UITableView+PaginationWrappable.swift */; }; + 67745269206249360024EEEF /* UITableView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745267206249360024EEEF /* UITableView+PaginationWrappable.swift */; }; + 6774526A206249360024EEEF /* UITableView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745267206249360024EEEF /* UITableView+PaginationWrappable.swift */; }; + 6774526C206249E30024EEEF /* UICollectionView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774526B206249E30024EEEF /* UICollectionView+PaginationWrappable.swift */; }; + 6774526D206249E30024EEEF /* UICollectionView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774526B206249E30024EEEF /* UICollectionView+PaginationWrappable.swift */; }; + 6774526E206249E30024EEEF /* UICollectionView+PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774526B206249E30024EEEF /* UICollectionView+PaginationWrappable.swift */; }; + 6774527020624A2A0024EEEF /* PaginationWrapperDelegate+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774526F20624A2A0024EEEF /* PaginationWrapperDelegate+DefaultImplementation.swift */; }; + 6774527120624A2A0024EEEF /* PaginationWrapperDelegate+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774526F20624A2A0024EEEF /* PaginationWrapperDelegate+DefaultImplementation.swift */; }; + 6774527420624E820024EEEF /* DataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774527320624E820024EEEF /* DataLoadingModel.swift */; }; + 6774527520624E820024EEEF /* DataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774527320624E820024EEEF /* DataLoadingModel.swift */; }; + 6774527620624E820024EEEF /* DataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774527320624E820024EEEF /* DataLoadingModel.swift */; }; + 6774527720624E820024EEEF /* DataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774527320624E820024EEEF /* DataLoadingModel.swift */; }; + 67745279206252020024EEEF /* DataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745278206252020024EEEF /* DataLoadingState.swift */; }; + 6774527A206252020024EEEF /* DataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745278206252020024EEEF /* DataLoadingState.swift */; }; + 6774527B206252020024EEEF /* DataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745278206252020024EEEF /* DataLoadingState.swift */; }; + 6774527C206252020024EEEF /* DataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745278206252020024EEEF /* DataLoadingState.swift */; }; + 67745280206256A20024EEEF /* RxDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774527F206256A20024EEEF /* RxDataLoadingModel.swift */; }; + 67745281206256A20024EEEF /* RxDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774527F206256A20024EEEF /* RxDataLoadingModel.swift */; }; + 67745282206256A20024EEEF /* RxDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774527F206256A20024EEEF /* RxDataLoadingModel.swift */; }; + 67745283206256A20024EEEF /* RxDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774527F206256A20024EEEF /* RxDataLoadingModel.swift */; }; + 67745286206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745285206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift */; }; + 67745287206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745285206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift */; }; + 67745288206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745285206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift */; }; + 67745289206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67745285206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift */; }; + 6774528D20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774528C20625C9E0024EEEF /* GeneralDataLoadingState.swift */; }; + 6774528E20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774528C20625C9E0024EEEF /* GeneralDataLoadingState.swift */; }; + 6774528F20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774528C20625C9E0024EEEF /* GeneralDataLoadingState.swift */; }; + 6774529020625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774528C20625C9E0024EEEF /* GeneralDataLoadingState.swift */; }; + 6774529220625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529120625D170024EEEF /* GeneralDataLoadingModel.swift */; }; + 6774529320625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529120625D170024EEEF /* GeneralDataLoadingModel.swift */; }; + 6774529420625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529120625D170024EEEF /* GeneralDataLoadingModel.swift */; }; + 6774529520625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529120625D170024EEEF /* GeneralDataLoadingModel.swift */; }; + 6774529A20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529920625E5B0024EEEF /* PaginationDataLoadingState.swift */; }; + 6774529B20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529920625E5B0024EEEF /* PaginationDataLoadingState.swift */; }; + 6774529C20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529920625E5B0024EEEF /* PaginationDataLoadingState.swift */; }; + 6774529D20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529920625E5B0024EEEF /* PaginationDataLoadingState.swift */; }; + 6774529F20625EEE0024EEEF /* PaginationDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529E20625EEE0024EEEF /* PaginationDataLoadingModel.swift */; }; + 677452A020625EEE0024EEEF /* PaginationDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529E20625EEE0024EEEF /* PaginationDataLoadingModel.swift */; }; + 677452A120625EEE0024EEEF /* PaginationDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529E20625EEE0024EEEF /* PaginationDataLoadingModel.swift */; }; + 677452A220625EEE0024EEEF /* PaginationDataLoadingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6774529E20625EEE0024EEEF /* PaginationDataLoadingModel.swift */; }; + 677452A420625FA90024EEEF /* RxDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452A320625FA90024EEEF /* RxDataSource.swift */; }; + 677452A520625FA90024EEEF /* RxDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452A320625FA90024EEEF /* RxDataSource.swift */; }; + 677452A620625FA90024EEEF /* RxDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452A320625FA90024EEEF /* RxDataSource.swift */; }; + 677452A720625FA90024EEEF /* RxDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452A320625FA90024EEEF /* RxDataSource.swift */; }; + 677452A9206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452A8206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift */; }; + 677452AA206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452A8206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift */; }; + 677452AB206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452A8206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift */; }; + 677452AC206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452A8206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift */; }; + 677452AE206274630024EEEF /* PaginationWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 677452AD206274630024EEEF /* PaginationWrapper.swift */; }; + 677452B720627FE00024EEEF /* PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FF7206175F700BDD9FB /* PaginationWrappable.swift */; }; + 677452B820627FE00024EEEF /* PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FF7206175F700BDD9FB /* PaginationWrappable.swift */; }; 6782BBA91EB31D5A0086E0B8 /* LeadKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6782BBA01EB31D590086E0B8 /* LeadKit.framework */; }; 67952C3C1EB3266100B3BA1A /* LeadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 67186B201EB247A200CFAFFB /* LeadKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 67952C3D1EB3266200B3BA1A /* LeadKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 67186B201EB247A200CFAFFB /* LeadKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -336,14 +381,14 @@ 67EB7FD520615D1700BDD9FB /* ResettableCursorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD320615D1700BDD9FB /* ResettableCursorType.swift */; }; 67EB7FD620615D1700BDD9FB /* ResettableCursorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD320615D1700BDD9FB /* ResettableCursorType.swift */; }; 67EB7FD720615D1700BDD9FB /* ResettableCursorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD320615D1700BDD9FB /* ResettableCursorType.swift */; }; - 67EB7FDA20615D5B00BDD9FB /* ResettableCursorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD920615D5B00BDD9FB /* ResettableCursorDataSource.swift */; }; - 67EB7FDB20615D5B00BDD9FB /* ResettableCursorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD920615D5B00BDD9FB /* ResettableCursorDataSource.swift */; }; - 67EB7FDC20615D5B00BDD9FB /* ResettableCursorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD920615D5B00BDD9FB /* ResettableCursorDataSource.swift */; }; - 67EB7FDD20615D5B00BDD9FB /* ResettableCursorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD920615D5B00BDD9FB /* ResettableCursorDataSource.swift */; }; - 67EB7FE420615DE000BDD9FB /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FE320615DE000BDD9FB /* DataSourceProtocol.swift */; }; - 67EB7FE520615DE000BDD9FB /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FE320615DE000BDD9FB /* DataSourceProtocol.swift */; }; - 67EB7FE620615DE000BDD9FB /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FE320615DE000BDD9FB /* DataSourceProtocol.swift */; }; - 67EB7FE720615DE000BDD9FB /* DataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FE320615DE000BDD9FB /* DataSourceProtocol.swift */; }; + 67EB7FDA20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD920615D5B00BDD9FB /* ResettableRxCursorDataSource.swift */; }; + 67EB7FDB20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD920615D5B00BDD9FB /* ResettableRxCursorDataSource.swift */; }; + 67EB7FDC20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD920615D5B00BDD9FB /* ResettableRxCursorDataSource.swift */; }; + 67EB7FDD20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FD920615D5B00BDD9FB /* ResettableRxCursorDataSource.swift */; }; + 67EB7FE420615DE000BDD9FB /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FE320615DE000BDD9FB /* DataSource.swift */; }; + 67EB7FE520615DE000BDD9FB /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FE320615DE000BDD9FB /* DataSource.swift */; }; + 67EB7FE620615DE000BDD9FB /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FE320615DE000BDD9FB /* DataSource.swift */; }; + 67EB7FE720615DE000BDD9FB /* DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FE320615DE000BDD9FB /* DataSource.swift */; }; 67EB7FEB2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FEA2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift */; }; 67EB7FEC2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FEA2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift */; }; 67EB7FED2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FEA2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift */; }; @@ -352,6 +397,13 @@ 67EB7FF22061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FF02061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift */; }; 67EB7FF32061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FF02061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift */; }; 67EB7FF42061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FF02061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift */; }; + 67EB7FF8206175F700BDD9FB /* PaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FF7206175F700BDD9FB /* PaginationWrappable.swift */; }; + 67EB7FFD206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappable.swift */; }; + 67EB7FFE206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappable.swift */; }; + 67EB7FFF206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappable.swift */; }; + 67EB8001206177D600BDD9FB /* PaginationWrapperDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB8000206177D600BDD9FB /* PaginationWrapperDelegate.swift */; }; + 67EB8002206177D600BDD9FB /* PaginationWrapperDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB8000206177D600BDD9FB /* PaginationWrapperDelegate.swift */; }; + 67EB8003206177D600BDD9FB /* PaginationWrapperDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB8000206177D600BDD9FB /* PaginationWrapperDelegate.swift */; }; 67FDC25F1FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; }; 67FDC2601FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; }; 67FDC2611FA310EA00C76A77 /* RequestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FDC25E1FA310EA00C76A77 /* RequestError.swift */; }; @@ -456,8 +508,6 @@ 671461CC1EB3396E00EAB194 /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 671461CD1EB3396E00EAB194 /* Log.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = ""; }; 671461CE1EB3396E00EAB194 /* LogFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogFormatter.swift; sourceTree = ""; }; - 671461D01EB3396E00EAB194 /* PaginationTableViewWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationTableViewWrapper.swift; sourceTree = ""; }; - 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationViewModel.swift; sourceTree = ""; }; 671461D31EB3396E00EAB194 /* NetworkService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = ""; }; 671461D51EB3396E00EAB194 /* XibView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XibView.swift; sourceTree = ""; }; 671461D71EB3396E00EAB194 /* CursorError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorError.swift; sourceTree = ""; }; @@ -474,7 +524,6 @@ 671461E91EB3396E00EAB194 /* CursorType+Slice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CursorType+Slice.swift"; sourceTree = ""; }; 671461EB1EB3396E00EAB194 /* Double+Rounding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Double+Rounding.swift"; sourceTree = ""; }; 671461F11EB3396E00EAB194 /* Observable+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+DeferredJust.swift"; sourceTree = ""; }; - 671461F41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PaginationTableViewWrapperDelegate+DefaultImplementation.swift"; sourceTree = ""; }; 671461F61EB3396E00EAB194 /* Sequence+ConcurrentMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sequence+ConcurrentMap.swift"; sourceTree = ""; }; 671461FC1EB3396E00EAB194 /* String+Localization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Localization.swift"; sourceTree = ""; }; 671461FD1EB3396E00EAB194 /* String+SizeCalculation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+SizeCalculation.swift"; sourceTree = ""; }; @@ -535,6 +584,20 @@ 6771DFDD1EE99F6F002DCDAE /* DateFormattingArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormattingArguments.swift; sourceTree = ""; }; 6771DFE31EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormattingArguments+DateFormatter.swift"; sourceTree = ""; }; 6771DFE91EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateFormattingService+MappingTransform.swift"; sourceTree = ""; }; + 67745267206249360024EEEF /* UITableView+PaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+PaginationWrappable.swift"; sourceTree = ""; }; + 6774526B206249E30024EEEF /* UICollectionView+PaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+PaginationWrappable.swift"; sourceTree = ""; }; + 6774526F20624A2A0024EEEF /* PaginationWrapperDelegate+DefaultImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaginationWrapperDelegate+DefaultImplementation.swift"; sourceTree = ""; }; + 6774527320624E820024EEEF /* DataLoadingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataLoadingModel.swift; sourceTree = ""; }; + 67745278206252020024EEEF /* DataLoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataLoadingState.swift; sourceTree = ""; }; + 6774527F206256A20024EEEF /* RxDataLoadingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxDataLoadingModel.swift; sourceTree = ""; }; + 67745285206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Rx+RxDataSourceProtocol.swift"; sourceTree = ""; }; + 6774528C20625C9E0024EEEF /* GeneralDataLoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralDataLoadingState.swift; sourceTree = ""; }; + 6774529120625D170024EEEF /* GeneralDataLoadingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralDataLoadingModel.swift; sourceTree = ""; }; + 6774529920625E5B0024EEEF /* PaginationDataLoadingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationDataLoadingState.swift; sourceTree = ""; }; + 6774529E20625EEE0024EEEF /* PaginationDataLoadingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationDataLoadingModel.swift; sourceTree = ""; }; + 677452A320625FA90024EEEF /* RxDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxDataSource.swift; sourceTree = ""; }; + 677452A8206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CursorType+RxDataSourceDefaultImplementation.swift"; sourceTree = ""; }; + 677452AD206274630024EEEF /* PaginationWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationWrapper.swift; sourceTree = ""; }; 6782BB911EB31CFE0086E0B8 /* LeadKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LeadKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6782BBA01EB31D590086E0B8 /* LeadKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LeadKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6782BBA81EB31D5A0086E0B8 /* LeadKit tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LeadKit tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -555,10 +618,13 @@ 67EB7FC6206148D000BDD9FB /* TotalCountCursorListingResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalCountCursorListingResult.swift; sourceTree = ""; }; 67EB7FCE20615B8900BDD9FB /* TotalCountCursorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalCountCursorConfiguration.swift; sourceTree = ""; }; 67EB7FD320615D1700BDD9FB /* ResettableCursorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResettableCursorType.swift; sourceTree = ""; }; - 67EB7FD920615D5B00BDD9FB /* ResettableCursorDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResettableCursorDataSource.swift; sourceTree = ""; }; - 67EB7FE320615DE000BDD9FB /* DataSourceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSourceProtocol.swift; sourceTree = ""; }; + 67EB7FD920615D5B00BDD9FB /* ResettableRxCursorDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResettableRxCursorDataSource.swift; sourceTree = ""; }; + 67EB7FE320615DE000BDD9FB /* DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataSource.swift; sourceTree = ""; }; 67EB7FEA2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTotalCountCursorListingResult.swift; sourceTree = ""; }; 67EB7FF02061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift"; sourceTree = ""; }; + 67EB7FF7206175F700BDD9FB /* PaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationWrappable.swift; sourceTree = ""; }; + 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyPaginationWrappable.swift; sourceTree = ""; }; + 67EB8000206177D600BDD9FB /* PaginationWrapperDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationWrapperDelegate.swift; sourceTree = ""; }; 67FDC25E1FA310EA00C76A77 /* RequestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestError.swift; sourceTree = ""; }; 78405D3B3D3C3E17456877FF /* Pods_LeadKit_iOSTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LeadKit_iOSTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 82F8BB171F5DDED100C1061B /* Single+DeferredJust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Single+DeferredJust.swift"; sourceTree = ""; }; @@ -666,9 +732,8 @@ 671461C41EB3396E00EAB194 /* Classes */ = { isa = PBXGroup; children = ( - 671461C71EB3396E00EAB194 /* Cursors */, + 6774527E2062566D0024EEEF /* DataLoading */, 671461CB1EB3396E00EAB194 /* Logging */, - 671461CF1EB3396E00EAB194 /* Pagination */, 671461D21EB3396E00EAB194 /* Services */, 671461D41EB3396E00EAB194 /* Views */, ); @@ -697,13 +762,13 @@ path = Logging; sourceTree = ""; }; - 671461CF1EB3396E00EAB194 /* Pagination */ = { + 671461CF1EB3396E00EAB194 /* PaginationDataLoading */ = { isa = PBXGroup; children = ( - 671461D01EB3396E00EAB194 /* PaginationTableViewWrapper.swift */, - 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */, + 6774529E20625EEE0024EEEF /* PaginationDataLoadingModel.swift */, + 677452AD206274630024EEEF /* PaginationWrapper.swift */, ); - path = Pagination; + path = PaginationDataLoading; sourceTree = ""; }; 671461D21EB3396E00EAB194 /* Services */ = { @@ -730,7 +795,7 @@ 671461D61EB3396E00EAB194 /* Enums */ = { isa = PBXGroup; children = ( - 671461D71EB3396E00EAB194 /* CursorError.swift */, + 6774528A20625C860024EEEF /* DataLoading */, 671461D81EB3396E00EAB194 /* LeadKitError.swift */, 671461D91EB3396E00EAB194 /* ResizeMode.swift */, 67FDC25E1FA310EA00C76A77 /* RequestError.swift */, @@ -749,14 +814,12 @@ 671461E21EB3396E00EAB194 /* CGImage */, 671461E51EB3396E00EAB194 /* CGSize */, A6D10EA91F8A9269003E69DD /* Comparable */, - 67EB7FEF2061681A00BDD9FB /* Cursors */, - 671461E81EB3396E00EAB194 /* CursorType */, + 67745284206259C20024EEEF /* DataLoading */, 6771DFE21EE99FF3002DCDAE /* DateFormattingArguments */, 6771DFE81EEA7C8F002DCDAE /* DateFormattingService */, 671461EA1EB3396E00EAB194 /* Double */, 6714639C1EB33AC200EAB194 /* NetworkService */, 671461F01EB3396E00EAB194 /* Observable */, - 671461F31EB3396E00EAB194 /* PaginationTableViewWrapperDelegate */, 671461F51EB3396E00EAB194 /* Sequence */, 82F8BB161F5DDED100C1061B /* Single */, 671461FA1EB3396E00EAB194 /* String */, @@ -822,6 +885,7 @@ isa = PBXGroup; children = ( 671461E91EB3396E00EAB194 /* CursorType+Slice.swift */, + 677452A8206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift */, ); path = CursorType; sourceTree = ""; @@ -842,14 +906,6 @@ path = Observable; sourceTree = ""; }; - 671461F31EB3396E00EAB194 /* PaginationTableViewWrapperDelegate */ = { - isa = PBXGroup; - children = ( - 671461F41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift */, - ); - path = PaginationTableViewWrapperDelegate; - sourceTree = ""; - }; 671461F51EB3396E00EAB194 /* Sequence */ = { isa = PBXGroup; children = ( @@ -955,15 +1011,14 @@ 671462221EB3396E00EAB194 /* Protocols */ = { isa = PBXGroup; children = ( - 67EB7FE820615E5000BDD9FB /* Cursors */, - 67EB7FC5206148C400BDD9FB /* Loading */, - 671462331EB3396E00EAB194 /* ConfigurableView.swift */, + 67EB7FC5206148C400BDD9FB /* DataLoading */, + 67EB7FF5206175CD00BDD9FB /* Views */, 679C77D41F98F78E0094BE10 /* AlertRepresentable.swift */, 671463A11EB33FF600EAB194 /* Animatable.swift */, 40F118461F8FEF97004AADAF /* AppearanceConfigurable.swift */, 671462231EB3396E00EAB194 /* BaseViewModel.swift */, 671462241EB3396E00EAB194 /* ConfigurableController.swift */, - 671462251EB3396E00EAB194 /* CursorType.swift */, + 671462331EB3396E00EAB194 /* ConfigurableView.swift */, 671462261EB3396E00EAB194 /* DrawingOperation.swift */, 671462281EB3396E00EAB194 /* LoadingIndicator.swift */, 671462291EB3396E00EAB194 /* ModuleConfigurator.swift */, @@ -981,7 +1036,7 @@ 671462351EB3396E00EAB194 /* Structures */ = { isa = PBXGroup; children = ( - 67EB7FE92061666900BDD9FB /* Cursors */, + 677452B620627F9E0024EEEF /* DataLoading */, 671462361EB3396E00EAB194 /* Api */, 6771DFDC1EE99F52002DCDAE /* DateFormatting */, 671462381EB3396E00EAB194 /* DrawingOperations */, @@ -1121,6 +1176,90 @@ path = DateFormattingService; sourceTree = ""; }; + 67745266206248F00024EEEF /* PaginationDataLoading */ = { + isa = PBXGroup; + children = ( + 6774526F20624A2A0024EEEF /* PaginationWrapperDelegate+DefaultImplementation.swift */, + 6774526B206249E30024EEEF /* UICollectionView+PaginationWrappable.swift */, + 67745267206249360024EEEF /* UITableView+PaginationWrappable.swift */, + ); + path = PaginationDataLoading; + sourceTree = ""; + }; + 6774527E2062566D0024EEEF /* DataLoading */ = { + isa = PBXGroup; + children = ( + 671461C71EB3396E00EAB194 /* Cursors */, + 6774529620625DA80024EEEF /* GeneralDataLoading */, + 671461CF1EB3396E00EAB194 /* PaginationDataLoading */, + 6774527F206256A20024EEEF /* RxDataLoadingModel.swift */, + ); + path = DataLoading; + sourceTree = ""; + }; + 67745284206259C20024EEEF /* DataLoading */ = { + isa = PBXGroup; + children = ( + 67EB7FEF2061681A00BDD9FB /* Cursors */, + 671461E81EB3396E00EAB194 /* CursorType */, + 67745266206248F00024EEEF /* PaginationDataLoading */, + 67745285206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift */, + ); + path = DataLoading; + sourceTree = ""; + }; + 6774528A20625C860024EEEF /* DataLoading */ = { + isa = PBXGroup; + children = ( + 6774528B20625C8D0024EEEF /* GeneralDataLoading */, + 6774529820625E290024EEEF /* PaginationDataLoading */, + 671461D71EB3396E00EAB194 /* CursorError.swift */, + ); + path = DataLoading; + sourceTree = ""; + }; + 6774528B20625C8D0024EEEF /* GeneralDataLoading */ = { + isa = PBXGroup; + children = ( + 6774528C20625C9E0024EEEF /* GeneralDataLoadingState.swift */, + ); + path = GeneralDataLoading; + sourceTree = ""; + }; + 6774529620625DA80024EEEF /* GeneralDataLoading */ = { + isa = PBXGroup; + children = ( + 6774529120625D170024EEEF /* GeneralDataLoadingModel.swift */, + ); + path = GeneralDataLoading; + sourceTree = ""; + }; + 6774529820625E290024EEEF /* PaginationDataLoading */ = { + isa = PBXGroup; + children = ( + 6774529920625E5B0024EEEF /* PaginationDataLoadingState.swift */, + ); + path = PaginationDataLoading; + sourceTree = ""; + }; + 677452B620627F9E0024EEEF /* DataLoading */ = { + isa = PBXGroup; + children = ( + 67EB7FE92061666900BDD9FB /* Cursors */, + 677452BA2062812C0024EEEF /* PaginationDataLoading */, + ); + path = DataLoading; + sourceTree = ""; + }; + 677452BA2062812C0024EEEF /* PaginationDataLoading */ = { + isa = PBXGroup; + children = ( + 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappable.swift */, + 67EB8000206177D600BDD9FB /* PaginationWrapperDelegate.swift */, + ); + path = PaginationDataLoading; + sourceTree = ""; + }; 679C77D51F98F7A60094BE10 /* UIAlertController */ = { isa = PBXGroup; children = ( @@ -1137,29 +1276,35 @@ path = CABasicAnimation; sourceTree = ""; }; - 67EB7FC5206148C400BDD9FB /* Loading */ = { + 67EB7FC5206148C400BDD9FB /* DataLoading */ = { isa = PBXGroup; children = ( - 67EB7FD820615D3100BDD9FB /* Pagination */, - 67EB7FE320615DE000BDD9FB /* DataSourceProtocol.swift */, + 67EB7FE820615E5000BDD9FB /* Cursors */, + 67EB7FD820615D3100BDD9FB /* PaginationDataLoading */, + 67EB7FE320615DE000BDD9FB /* DataSource.swift */, + 6774527320624E820024EEEF /* DataLoadingModel.swift */, + 67745278206252020024EEEF /* DataLoadingState.swift */, + 677452A320625FA90024EEEF /* RxDataSource.swift */, ); - path = Loading; + path = DataLoading; sourceTree = ""; }; - 67EB7FD820615D3100BDD9FB /* Pagination */ = { + 67EB7FD820615D3100BDD9FB /* PaginationDataLoading */ = { isa = PBXGroup; children = ( + 67EB7FF7206175F700BDD9FB /* PaginationWrappable.swift */, 67EB7FCE20615B8900BDD9FB /* TotalCountCursorConfiguration.swift */, 67EB7FC6206148D000BDD9FB /* TotalCountCursorListingResult.swift */, ); - path = Pagination; + path = PaginationDataLoading; sourceTree = ""; }; 67EB7FE820615E5000BDD9FB /* Cursors */ = { isa = PBXGroup; children = ( - 67EB7FD920615D5B00BDD9FB /* ResettableCursorDataSource.swift */, + 671462251EB3396E00EAB194 /* CursorType.swift */, 67EB7FD320615D1700BDD9FB /* ResettableCursorType.swift */, + 67EB7FD920615D5B00BDD9FB /* ResettableRxCursorDataSource.swift */, ); path = Cursors; sourceTree = ""; @@ -1180,6 +1325,13 @@ path = Cursors; sourceTree = ""; }; + 67EB7FF5206175CD00BDD9FB /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; 78CFEE201C5C456B00F50370 = { isa = PBXGroup; children = ( @@ -2082,8 +2234,11 @@ EFBE57D01EC35EF20040E00A /* Array+Extensions.swift in Sources */, 671462E41EB3396E00EAB194 /* UIColor+Hex.swift in Sources */, 67EB7FF12061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */, + 67EB8001206177D600BDD9FB /* PaginationWrapperDelegate.swift in Sources */, 671462CC1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */, + 6774529A20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */, 671462801EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */, + 67EB7FF8206175F700BDD9FB /* PaginationWrappable.swift in Sources */, 671463541EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 671463601EB3396E00EAB194 /* SupportProtocol.swift in Sources */, 671462841EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */, @@ -2097,7 +2252,6 @@ 671463101EB3396E00EAB194 /* UIViewController+DefaultXibName.swift in Sources */, 674AF55C1EC45B1600038A8F /* UIActivityIndicatorView+LoadingIndicator.swift in Sources */, 671463401EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */, - 671462641EB3396E00EAB194 /* PaginationViewModel.swift in Sources */, 67A1FF8F1EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */, 671462901EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, 671462FC1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */, @@ -2105,6 +2259,7 @@ 36DAAF512007CC920090BE0D /* UITableView+Extensions.swift in Sources */, 671463841EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 6771DFD81EE99EBA002DCDAE /* DateFormattingService.swift in Sources */, + 6774528D20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */, 671462D01EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */, 671463901EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */, A658E54D1F8CD7790093527A /* TableRow+SeparatorsExtensions.swift in Sources */, @@ -2115,13 +2270,13 @@ 82F8BB181F5DDED100C1061B /* Single+DeferredJust.swift in Sources */, 671463301EB3396E00EAB194 /* CursorType.swift in Sources */, 67FDC25F1FA310EA00C76A77 /* RequestError.swift in Sources */, + 67745268206249360024EEEF /* UITableView+PaginationWrappable.swift in Sources */, 6714624C1EB3396E00EAB194 /* MapCursor.swift in Sources */, A6C9A4FA1F8BBCF2009311CC /* EmptyCell.swift in Sources */, 671463241EB3396E00EAB194 /* Any+TypeName.swift in Sources */, 671463881EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */, A6D10EAB1F8A9278003E69DD /* Comparable+Extensions.swift in Sources */, 671463801EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */, - 671462601EB3396E00EAB194 /* PaginationTableViewWrapper.swift in Sources */, 671463281EB3396E00EAB194 /* BaseViewModel.swift in Sources */, A6E0DDDF1F8A696F002CA74E /* SeparatorCell.swift in Sources */, 671462AC1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */, @@ -2130,16 +2285,18 @@ 6714627C1EB3396E00EAB194 /* AlamofireManager+Extensions.swift in Sources */, 671462D41EB3396E00EAB194 /* TableDirector+Extensions.swift in Sources */, 671462581EB3396E00EAB194 /* Log.swift in Sources */, - 671462B41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift in Sources */, 671462781EB3396E00EAB194 /* ResizeMode.swift in Sources */, A676AE551F98112E001F9214 /* ObservableMappable.swift in Sources */, A6E0DDE11F8A696F002CA74E /* SeparatorRowBox.swift in Sources */, A6E0DDDE1F8A696F002CA74E /* EmptyCellRow.swift in Sources */, + 67EB7FFD206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */, 671463041EB3396E00EAB194 /* UIView+LoadingIndicator.swift in Sources */, + 6774527420624E820024EEEF /* DataLoadingModel.swift in Sources */, 40F118491F8FF223004AADAF /* TableRow+AppearanceExtension.swift in Sources */, 671463701EB3396E00EAB194 /* ApiRequestParameters.swift in Sources */, A658E5501F8CD9350093527A /* Array+SeparatorRowBoxExtensions.swift in Sources */, 671462EC1EB3396E00EAB194 /* UIImage+Extensions.swift in Sources */, + 677452AE206274630024EEEF /* PaginationWrapper.swift in Sources */, A6E0DDF11F8A6C80002CA74E /* SeparatorConfiguration.swift in Sources */, 67EB7FEB2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift in Sources */, 6714636C1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */, @@ -2148,17 +2305,21 @@ 6714625C1EB3396E00EAB194 /* LogFormatter.swift in Sources */, 671463081EB3396E00EAB194 /* UIView+Rotation.swift in Sources */, 6714626C1EB3396E00EAB194 /* XibView.swift in Sources */, + 6774529220625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */, 6714637C1EB3396E00EAB194 /* ImageDrawingOperation.swift in Sources */, 671463341EB3396E00EAB194 /* DrawingOperation.swift in Sources */, 671462701EB3396E00EAB194 /* CursorError.swift in Sources */, 671463981EB3396E00EAB194 /* AnyLoadingIndicator.swift in Sources */, 671463A71EB340C000EAB194 /* UIViewController+ConfigurableController.swift in Sources */, 671463141EB3396E00EAB194 /* UIViewController+TopVisibleViewController.swift in Sources */, + 6774527020624A2A0024EEEF /* PaginationWrapperDelegate+DefaultImplementation.swift in Sources */, + 67745280206256A20024EEEF /* RxDataLoadingModel.swift in Sources */, A6F32C081F6EBDAA00AC08EE /* String+LocalizedComponent.swift in Sources */, 671462881EB3396E00EAB194 /* CGFloat+Pixels.swift in Sources */, 671462941EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */, + 67745279206252020024EEEF /* DataLoadingState.swift in Sources */, 671463641EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */, - 67EB7FDA20615D5B00BDD9FB /* ResettableCursorDataSource.swift in Sources */, + 67EB7FDA20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */, 671462481EB3396E00EAB194 /* FixedPageCursor.swift in Sources */, 671462C81EB3396E00EAB194 /* String+Localization.swift in Sources */, 671462B81EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */, @@ -2173,14 +2334,19 @@ 6714632C1EB3396E00EAB194 /* ConfigurableController.swift in Sources */, 6714628C1EB3396E00EAB194 /* CGImage+Alpha.swift in Sources */, 671462741EB3396E00EAB194 /* LeadKitError.swift in Sources */, + 677452A420625FA90024EEEF /* RxDataSource.swift in Sources */, 671462D81EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */, 6714638C1EB3396E00EAB194 /* SolidFillDrawingOperation.swift in Sources */, + 6774526C206249E30024EEEF /* UICollectionView+PaginationWrappable.swift in Sources */, 671463A21EB33FF600EAB194 /* Animatable.swift in Sources */, + 677452A9206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */, 671462501EB3396E00EAB194 /* StaticCursor.swift in Sources */, 67EB7FC7206148D000BDD9FB /* TotalCountCursorListingResult.swift in Sources */, - 67EB7FE420615DE000BDD9FB /* DataSourceProtocol.swift in Sources */, + 67745286206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */, + 67EB7FE420615DE000BDD9FB /* DataSource.swift in Sources */, 67EB7FD420615D1700BDD9FB /* ResettableCursorType.swift in Sources */, 6714629C1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */, + 6774529F20625EEE0024EEEF /* PaginationDataLoadingModel.swift in Sources */, 671463681EB3396E00EAB194 /* ConfigurableView.swift in Sources */, 6771DFE41EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift in Sources */, ); @@ -2211,7 +2377,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 67EB7FE620615DE000BDD9FB /* DataSourceProtocol.swift in Sources */, + 67EB7FE620615DE000BDD9FB /* DataSource.swift in Sources */, 6714634A1EB3396E00EAB194 /* ResettableType.swift in Sources */, 671462E61EB3396E00EAB194 /* UIColor+Hex.swift in Sources */, 671462CE1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */, @@ -2219,31 +2385,36 @@ EFBE57D21EC35EF20040E00A /* Array+Extensions.swift in Sources */, 67EB7FC2206140E600BDD9FB /* TotalCountCursor.swift in Sources */, 671462821EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */, + 67745288206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */, 67E6C2371EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */, 671463561EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 6771DFE01EE99F6F002DCDAE /* DateFormattingArguments.swift in Sources */, 671463621EB3396E00EAB194 /* SupportProtocol.swift in Sources */, 6771DFEC1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */, 671462861EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */, + 6774527B206252020024EEEF /* DataLoadingState.swift in Sources */, 6714634E1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */, 6714626A1EB3396E00EAB194 /* NetworkService.swift in Sources */, 671463421EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */, - 671462661EB3396E00EAB194 /* PaginationViewModel.swift in Sources */, 671462921EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, 671463861EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 671463921EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */, + 67745282206256A20024EEEF /* RxDataLoadingModel.swift in Sources */, 6714629A1EB3396E00EAB194 /* CGSize+Resize.swift in Sources */, 671463321EB3396E00EAB194 /* CursorType.swift in Sources */, 6714624E1EB3396E00EAB194 /* MapCursor.swift in Sources */, A676AE571F981130001F9214 /* ObservableMappable.swift in Sources */, + 6774528F20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */, 671463261EB3396E00EAB194 /* Any+TypeName.swift in Sources */, 6714638A1EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */, + 6774529C20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */, 671463821EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */, 6714632A1EB3396E00EAB194 /* BaseViewModel.swift in Sources */, 671462AE1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */, - 67EB7FDC20615D5B00BDD9FB /* ResettableCursorDataSource.swift in Sources */, + 67EB7FDC20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */, A6F32C0B1F6EBE5C00AC08EE /* String+LocalizedComponent.swift in Sources */, 6714627E1EB3396E00EAB194 /* AlamofireManager+Extensions.swift in Sources */, + 677452AB206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */, 6771DFDA1EE99EBA002DCDAE /* DateFormattingService.swift in Sources */, 67EB7FD620615D1700BDD9FB /* ResettableCursorType.swift in Sources */, 6714625A1EB3396E00EAB194 /* Log.swift in Sources */, @@ -2264,17 +2435,21 @@ 671463661EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */, 67EB7FD120615B8900BDD9FB /* TotalCountCursorConfiguration.swift in Sources */, 67EB7FC9206148D000BDD9FB /* TotalCountCursorListingResult.swift in Sources */, + 6774527620624E820024EEEF /* DataLoadingModel.swift in Sources */, 6771DFE61EE9A00A002DCDAE /* DateFormattingArguments+DateFormatter.swift in Sources */, 6714624A1EB3396E00EAB194 /* FixedPageCursor.swift in Sources */, 671462CA1EB3396E00EAB194 /* String+Localization.swift in Sources */, + 677452A620625FA90024EEEF /* RxDataSource.swift in Sources */, 671462BA1EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */, 671463761EB3396E00EAB194 /* BorderDrawingOperation.swift in Sources */, + 677452A120625EEE0024EEEF /* PaginationDataLoadingModel.swift in Sources */, 671462561EB3396E00EAB194 /* App.swift in Sources */, 675C1FB51F97CA33007D5249 /* AppearanceConfigurable.swift in Sources */, 6714628E1EB3396E00EAB194 /* CGImage+Alpha.swift in Sources */, 671462761EB3396E00EAB194 /* LeadKitError.swift in Sources */, 671462DA1EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */, 6714638E1EB3396E00EAB194 /* SolidFillDrawingOperation.swift in Sources */, + 6774529420625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */, 67FDC2611FA310EA00C76A77 /* RequestError.swift in Sources */, 671462521EB3396E00EAB194 /* StaticCursor.swift in Sources */, 6714629E1EB3396E00EAB194 /* CursorType+Slice.swift in Sources */, @@ -2290,6 +2465,9 @@ 671462E71EB3396E00EAB194 /* UIColor+Hex.swift in Sources */, 671462CF1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */, 671462831EB3396E00EAB194 /* AlamofireRequest+Extensions.swift in Sources */, + 677452B820627FE00024EEEF /* PaginationWrappable.swift in Sources */, + 67745283206256A20024EEEF /* RxDataLoadingModel.swift in Sources */, + 67EB8003206177D600BDD9FB /* PaginationWrapperDelegate.swift in Sources */, 671463571EB3396E00EAB194 /* StaticViewHeightProtocol.swift in Sources */, 671463631EB3396E00EAB194 /* SupportProtocol.swift in Sources */, 671462871EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */, @@ -2299,9 +2477,8 @@ 6714626B1EB3396E00EAB194 /* NetworkService.swift in Sources */, 671463131EB3396E00EAB194 /* UIViewController+DefaultXibName.swift in Sources */, 671463431EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */, - 671462671EB3396E00EAB194 /* PaginationViewModel.swift in Sources */, 671462931EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, - 67EB7FE720615DE000BDD9FB /* DataSourceProtocol.swift in Sources */, + 67EB7FE720615DE000BDD9FB /* DataSource.swift in Sources */, 671462FF1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */, 67FDC2621FA310EA00C76A77 /* RequestError.swift in Sources */, 671463871EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, @@ -2315,9 +2492,13 @@ 67EB7FD220615B8900BDD9FB /* TotalCountCursorConfiguration.swift in Sources */, EFBE57D31EC35EF20040E00A /* Array+Extensions.swift in Sources */, 6714638B1EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */, + 67EB7FFF206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */, A676AE4A1F97D28A001F9214 /* String+Extensions.swift in Sources */, 671463831EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */, + 6774527C206252020024EEEF /* DataLoadingState.swift in Sources */, + 677452AC206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */, 6714632B1EB3396E00EAB194 /* BaseViewModel.swift in Sources */, + 677452A220625EEE0024EEEF /* PaginationDataLoadingModel.swift in Sources */, A6F32C0C1F6EBE5C00AC08EE /* String+LocalizedComponent.swift in Sources */, 671462AF1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */, A676AE4F1F9810C1001F9214 /* Any+Cast.swift in Sources */, @@ -2326,12 +2507,15 @@ 6771DFDB1EE99EBA002DCDAE /* DateFormattingService.swift in Sources */, 6714625B1EB3396E00EAB194 /* Log.swift in Sources */, 6714627B1EB3396E00EAB194 /* ResizeMode.swift in Sources */, + 67745289206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */, 671463071EB3396E00EAB194 /* UIView+LoadingIndicator.swift in Sources */, + 6774526E206249E30024EEEF /* UICollectionView+PaginationWrappable.swift in Sources */, 6771DFED1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */, 671463A91EB340C000EAB194 /* UIViewController+ConfigurableController.swift in Sources */, A676AE581F981131001F9214 /* ObservableMappable.swift in Sources */, 671463731EB3396E00EAB194 /* ApiRequestParameters.swift in Sources */, 671462EF1EB3396E00EAB194 /* UIImage+Extensions.swift in Sources */, + 6774526A206249360024EEEF /* UITableView+PaginationWrappable.swift in Sources */, 6714636F1EB3396E00EAB194 /* XibNameProtocol.swift in Sources */, EFBE57DE1EC361620040E00A /* UIView+Layout.swift in Sources */, 67A1FF971EBCA65E00D6C89F /* CABasicAnimation+Rotation.swift in Sources */, @@ -2343,7 +2527,9 @@ 67EB7FD720615D1700BDD9FB /* ResettableCursorType.swift in Sources */, 671463371EB3396E00EAB194 /* DrawingOperation.swift in Sources */, 671462731EB3396E00EAB194 /* CursorError.swift in Sources */, - 67EB7FDD20615D5B00BDD9FB /* ResettableCursorDataSource.swift in Sources */, + 67EB7FDD20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */, + 677452A720625FA90024EEEF /* RxDataSource.swift in Sources */, + 6774527720624E820024EEEF /* DataLoadingModel.swift in Sources */, 6714639B1EB3396E00EAB194 /* AnyLoadingIndicator.swift in Sources */, 6771DFE11EE99F6F002DCDAE /* DateFormattingArguments.swift in Sources */, 671463171EB3396E00EAB194 /* UIViewController+TopVisibleViewController.swift in Sources */, @@ -2363,12 +2549,15 @@ 6714631B1EB3396E00EAB194 /* UIWindow+Extensions.swift in Sources */, 671462571EB3396E00EAB194 /* App.swift in Sources */, 6714637B1EB3396E00EAB194 /* CALayerDrawingOperation.swift in Sources */, + 6774529520625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */, + 6774529D20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */, 6714632F1EB3396E00EAB194 /* ConfigurableController.swift in Sources */, 67EB7FF42061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */, 6714628F1EB3396E00EAB194 /* CGImage+Alpha.swift in Sources */, 67051ADD1EBC7C36008EADC0 /* SpinnerView.swift in Sources */, 671462771EB3396E00EAB194 /* LeadKitError.swift in Sources */, 671462DB1EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */, + 6774529020625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */, 67EB7FC3206140E600BDD9FB /* TotalCountCursor.swift in Sources */, 6714638F1EB3396E00EAB194 /* SolidFillDrawingOperation.swift in Sources */, 67E6C2381EBB32F5007842A6 /* SingleLoadCursor.swift in Sources */, @@ -2396,11 +2585,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 67EB7FE520615DE000BDD9FB /* DataSourceProtocol.swift in Sources */, + 67EB7FE520615DE000BDD9FB /* DataSource.swift in Sources */, 671463491EB3396E00EAB194 /* ResettableType.swift in Sources */, A676AE491F97D28A001F9214 /* String+Extensions.swift in Sources */, 671462E51EB3396E00EAB194 /* UIColor+Hex.swift in Sources */, + 67EB7FFE206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */, 671462CD1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */, + 677452B720627FE00024EEEF /* PaginationWrappable.swift in Sources */, 67EB7FC1206140E600BDD9FB /* TotalCountCursor.swift in Sources */, 6771DFDF1EE99F6F002DCDAE /* DateFormattingArguments.swift in Sources */, 6771DFEB1EEA7CB8002DCDAE /* DateFormattingService+MappingTransform.swift in Sources */, @@ -2411,16 +2602,17 @@ 671463611EB3396E00EAB194 /* SupportProtocol.swift in Sources */, 671462851EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */, 6714634D1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */, + 67745281206256A20024EEEF /* RxDataLoadingModel.swift in Sources */, 671462F11EB3396E00EAB194 /* UIImage+SupportExtensions.swift in Sources */, 671462691EB3396E00EAB194 /* NetworkService.swift in Sources */, 671463111EB3396E00EAB194 /* UIViewController+DefaultXibName.swift in Sources */, 671463411EB3396E00EAB194 /* ModuleConfigurator.swift in Sources */, - 671462651EB3396E00EAB194 /* PaginationViewModel.swift in Sources */, 671462911EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, 67051ADC1EBC7C36008EADC0 /* SpinnerView.swift in Sources */, 671462FD1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */, 671463851EB3396E00EAB194 /* ResizeDrawingOperation.swift in Sources */, 671462D11EB3396E00EAB194 /* UIScrollView+Support.swift in Sources */, + 6774528E20625C9E0024EEEF /* GeneralDataLoadingState.swift in Sources */, 671463911EB3396E00EAB194 /* TemplateDrawingOperation.swift in Sources */, 67EB7FEC2061667900BDD9FB /* DefaultTotalCountCursorListingResult.swift in Sources */, 671462991EB3396E00EAB194 /* CGSize+Resize.swift in Sources */, @@ -2431,13 +2623,17 @@ 671463891EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */, 675C1FB61F97CA33007D5249 /* AppearanceConfigurable.swift in Sources */, 671463811EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */, - 67EB7FDB20615D5B00BDD9FB /* ResettableCursorDataSource.swift in Sources */, + 67EB7FDB20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */, A6F32C0A1F6EBE5B00AC08EE /* String+LocalizedComponent.swift in Sources */, 671463291EB3396E00EAB194 /* BaseViewModel.swift in Sources */, 671462AD1EB3396E00EAB194 /* Observable+DeferredJust.swift in Sources */, 671463011EB3396E00EAB194 /* UIView+LoadFromNib.swift in Sources */, + 6774526D206249E30024EEEF /* UICollectionView+PaginationWrappable.swift in Sources */, 6714627D1EB3396E00EAB194 /* AlamofireManager+Extensions.swift in Sources */, 671462591EB3396E00EAB194 /* Log.swift in Sources */, + 6774527120624A2A0024EEEF /* PaginationWrapperDelegate+DefaultImplementation.swift in Sources */, + 67EB8002206177D600BDD9FB /* PaginationWrapperDelegate.swift in Sources */, + 677452AA206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */, 671462791EB3396E00EAB194 /* ResizeMode.swift in Sources */, 671463051EB3396E00EAB194 /* UIView+LoadingIndicator.swift in Sources */, 671463711EB3396E00EAB194 /* ApiRequestParameters.swift in Sources */, @@ -2449,7 +2645,9 @@ 6714626D1EB3396E00EAB194 /* XibView.swift in Sources */, 6714637D1EB3396E00EAB194 /* ImageDrawingOperation.swift in Sources */, 671463351EB3396E00EAB194 /* DrawingOperation.swift in Sources */, + 677452A020625EEE0024EEEF /* PaginationDataLoadingModel.swift in Sources */, 67FDC2601FA310EA00C76A77 /* RequestError.swift in Sources */, + 6774529320625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */, 671462711EB3396E00EAB194 /* CursorError.swift in Sources */, 671463991EB3396E00EAB194 /* AnyLoadingIndicator.swift in Sources */, 671463A81EB340C000EAB194 /* UIViewController+ConfigurableController.swift in Sources */, @@ -2461,7 +2659,9 @@ 671462951EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */, 67EB7FF22061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */, 671463651EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */, + 6774527520624E820024EEEF /* DataLoadingModel.swift in Sources */, A6C9A5101F8BC79D009311CC /* Comparable+Extensions.swift in Sources */, + 677452A520625FA90024EEEF /* RxDataSource.swift in Sources */, 671462491EB3396E00EAB194 /* FixedPageCursor.swift in Sources */, 671462C91EB3396E00EAB194 /* String+Localization.swift in Sources */, 671462B91EB3396E00EAB194 /* Sequence+ConcurrentMap.swift in Sources */, @@ -2471,10 +2671,14 @@ EFBE57DC1EC361620040E00A /* UIView+Layout.swift in Sources */, 671462551EB3396E00EAB194 /* App.swift in Sources */, 67A1FF901EBCA09B00D6C89F /* UIImage+Spinner.swift in Sources */, + 6774529B20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */, 671463791EB3396E00EAB194 /* CALayerDrawingOperation.swift in Sources */, 6714632D1EB3396E00EAB194 /* ConfigurableController.swift in Sources */, + 67745287206259CF0024EEEF /* Rx+RxDataSourceProtocol.swift in Sources */, A676AE561F98112F001F9214 /* ObservableMappable.swift in Sources */, + 6774527A206252020024EEEF /* DataLoadingState.swift in Sources */, 6714628D1EB3396E00EAB194 /* CGImage+Alpha.swift in Sources */, + 67745269206249360024EEEF /* UITableView+PaginationWrappable.swift in Sources */, 67EB7FC8206148D000BDD9FB /* TotalCountCursorListingResult.swift in Sources */, 671462751EB3396E00EAB194 /* LeadKitError.swift in Sources */, EFBE57D11EC35EF20040E00A /* Array+Extensions.swift in Sources */, diff --git a/Sources/Classes/Cursors/FixedPageCursor.swift b/Sources/Classes/DataLoading/Cursors/FixedPageCursor.swift similarity index 94% rename from Sources/Classes/Cursors/FixedPageCursor.swift rename to Sources/Classes/DataLoading/Cursors/FixedPageCursor.swift index df4c4ca8..44094546 100644 --- a/Sources/Classes/Cursors/FixedPageCursor.swift +++ b/Sources/Classes/DataLoading/Cursors/FixedPageCursor.swift @@ -23,7 +23,9 @@ import RxSwift /// Paging cursor implementation with enclosed cursor for fetching results -public class FixedPageCursor: CursorType { +public class FixedPageCursor: CursorType, RxDataSource { + + public typealias ResultType = [Element] fileprivate let cursor: Cursor @@ -76,6 +78,8 @@ public class FixedPageCursor: CursorType { /// FixedPageCursor subclass with implementation of ResettableType public class ResettableFixedPageCursor: FixedPageCursor, ResettableType { + public typealias ResultType = [Element] + public override init(cursor: Cursor, pageSize: Int) { super.init(cursor: cursor, pageSize: pageSize) } diff --git a/Sources/Classes/Cursors/MapCursor.swift b/Sources/Classes/DataLoading/Cursors/MapCursor.swift similarity index 95% rename from Sources/Classes/Cursors/MapCursor.swift rename to Sources/Classes/DataLoading/Cursors/MapCursor.swift index 69647bc9..af29dff3 100644 --- a/Sources/Classes/Cursors/MapCursor.swift +++ b/Sources/Classes/DataLoading/Cursors/MapCursor.swift @@ -45,7 +45,9 @@ public extension CursorType { } /// Map cursor implementation with enclosed cursor for fetching results -public class MapCursor: CursorType { +public class MapCursor: CursorType, RxDataSource { + + public typealias ResultType = [Element] public typealias Transform = (Cursor.Element) -> T? @@ -91,6 +93,8 @@ public class MapCursor: CursorType { /// MapCursor subclass with implementation of ResettableType public class ResettableMapCursor: MapCursor, ResettableType { + public typealias ResultType = [Cursor.Element] + public override init(cursor: Cursor, transform: @escaping Transform) { super.init(cursor: cursor, transform: transform) } diff --git a/Sources/Classes/Cursors/SingleLoadCursor.swift b/Sources/Classes/DataLoading/Cursors/SingleLoadCursor.swift similarity index 100% rename from Sources/Classes/Cursors/SingleLoadCursor.swift rename to Sources/Classes/DataLoading/Cursors/SingleLoadCursor.swift diff --git a/Sources/Classes/Cursors/StaticCursor.swift b/Sources/Classes/DataLoading/Cursors/StaticCursor.swift similarity index 94% rename from Sources/Classes/Cursors/StaticCursor.swift rename to Sources/Classes/DataLoading/Cursors/StaticCursor.swift index fca71d1b..a4bca0c5 100644 --- a/Sources/Classes/Cursors/StaticCursor.swift +++ b/Sources/Classes/DataLoading/Cursors/StaticCursor.swift @@ -23,7 +23,9 @@ import RxSwift /// Stub cursor implementation for array content type -public class StaticCursor: ResettableCursorType { +public class StaticCursor: ResettableRxDataSourceCursor { + + public typealias ResultType = [Element] private let content: [Element] diff --git a/Sources/Classes/Cursors/TotalCountCursor.swift b/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift similarity index 81% rename from Sources/Classes/Cursors/TotalCountCursor.swift rename to Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift index beadaf6d..6acf47b0 100644 --- a/Sources/Classes/Cursors/TotalCountCursor.swift +++ b/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift @@ -23,19 +23,17 @@ import RxSwift import RxCocoa -public final class TotalCountCursor: ResettableCursorDataSource { +public final class TotalCountCursor: ResettableRxDataSourceCursor { - public typealias Element = CC.ListingType.ElementType - public typealias ResultType = [CC.ListingType.ElementType] + public typealias Element = CursorConfiguration.ResultType.ElementType + public typealias ResultType = [Element] - private let configuration: CC + private let configuration: CursorConfiguration private var elements: [Element] = [] public private(set) var totalCount: Int = .max - private let disposeBag = DisposeBag() - public var exhausted: Bool { return count >= totalCount } @@ -48,16 +46,16 @@ public final class TotalCountCursor: Resettab return elements[index] } - public init(configuration: CC) { + public init(configuration: CursorConfiguration) { self.configuration = configuration } public required init(resetFrom other: TotalCountCursor) { - self.configuration = other.configuration.reset() + configuration = other.configuration.reset() } public func loadNextBatch() -> Single<[Element]> { - return configuration.nextBatchObservable() + return configuration.resultSingle() .do(onSuccess: { [weak self] listingResult in self?.totalCount = listingResult.totalCount self?.elements = (self?.elements ?? []) + listingResult.results diff --git a/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingModel.swift b/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingModel.swift new file mode 100644 index 00000000..177c0d24 --- /dev/null +++ b/Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingModel.swift @@ -0,0 +1,26 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import RxSwift + +public final class GeneralDataLoadingModel: RxDataLoadingModel>> { +} diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift new file mode 100644 index 00000000..dc513b07 --- /dev/null +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift @@ -0,0 +1,105 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import RxSwift + +public final class PaginationDataLoadingModel: + RxDataLoadingModel> { + + private enum LoadType { + + case reload + case retry + case next + + } + + // Additional init with closure typealias fixes swift + // runtime crash inside emptyResultChecker. + + public typealias PaginationEmptyResultChecker = (Cursor.ResultType) -> Bool + + public override init(dataSource: Cursor, emptyResultChecker: @escaping PaginationEmptyResultChecker) { + super.init(dataSource: dataSource, emptyResultChecker: emptyResultChecker) + } + + override open func reload() { + load(.reload) + } + + override open func retry() { + load(.retry) + } + + public func loadMore() { + load(.next) + } + + private func load(_ loadType: LoadType) { + switch loadType { + case .reload, .retry: + dataSource = dataSource.reset() + + if loadType == .retry { + state = .initial + } + + state = .initialLoading(after: state) + case .next: + if case .exhausted = state { + fatalError("You shouldn't call load(.next) after got .exhausted state!") + } + + state = .loadingMore(after: state) + } + + requestResult(from: dataSource) + } + + override func onGot(error: Error) { + if case .exhausted? = error as? CursorError, case .initialLoading(let after) = state { + switch after { + case .initial, .empty: // cursor exhausted after creation + state = .empty + default: + super.onGot(error: error) + } + } else { + super.onGot(error: error) + } + } + + override func updateStateAfterNonEmptyResult(from dataSource: DataSourceType) { + if dataSource.exhausted { + state = .exhausted + } + } + +} + +extension PaginationDataLoadingModel { + + convenience init(cursor: Cursor) { + self.init(dataSource: cursor) { $0.isEmpty } + } + +} diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift new file mode 100644 index 00000000..ab81267a --- /dev/null +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift @@ -0,0 +1,371 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import RxSwift +import RxCocoa +import UIScrollView_InfiniteScroll + +/// Class that connects PaginationDataLoadingModel with UIScrollView. It handles all non-visual and visual states. +final public class PaginationWrapper + // "Segmentation fault: 11" in Xcode 9.2 without redundant same-type constraint :( + where Cursor == Delegate.DataSourceType, Cursor.ResultType == [Cursor.Element] { + + private typealias DataLoadingModel = PaginationDataLoadingModel + + private typealias LoadingState = DataLoadingModel.LoadingStateType + + private var wrappedView: AnyPaginationWrappable + private let paginationViewModel: DataLoadingModel + private weak var delegate: Delegate? + + /// Sets the offset between the real end of the scroll view content and the scroll position, + /// so the handler can be triggered before reaching end. Defaults to 0.0; + public var infiniteScrollTriggerOffset: CGFloat { + get { + return wrappedView.scrollView.infiniteScrollTriggerOffset + } + set { + wrappedView.scrollView.infiniteScrollTriggerOffset = newValue + } + } + + public var pullToRefreshEnabled: Bool = true { + didSet { + if pullToRefreshEnabled { + createRefreshControl() + } else { + removeRefreshControl() + } + } + } + + private let disposeBag = DisposeBag() + + private var currentPlaceholderView: UIView? + private var currentPlaceholderViewTopConstraint: NSLayoutConstraint? + + private let applicationCurrentyActive = Variable(true) + + /// Initializer with table view, placeholders container view, cusor and delegate parameters. + /// + /// - Parameters: + /// - wrappedView: UIScrollView instance to work with. + /// - cursor: Cursor object that acts as data source. + /// - delegate: Delegate object for data loading events handling and UI customization. + public init(wrappedView: AnyPaginationWrappable, cursor: Cursor, delegate: Delegate) { + self.wrappedView = wrappedView + self.delegate = delegate + + self.paginationViewModel = PaginationDataLoadingModel(cursor: cursor) + + bindViewModelStates() + + createRefreshControl() + + bindAppStateNotifications() + } + + /// Method that reload all data in internal view model. + public func reload() { + paginationViewModel.reload() + } + + /// Method acts like reload, but shows initial loading view after being invoked. + public func retry() { + paginationViewModel.retry() + } + + /// Method that enables placeholders animation due pull-to-refresh interaction. + /// + /// - Parameter scrollObservable: Observable that emits content offset as CGPoint. + public func setScrollObservable(_ scrollObservable: Observable) { + scrollObservable + .asDriver(onErrorJustReturn: .zero) + .drive(scrollOffsetChanged) + .disposed(by: disposeBag) + } + + // MARK: - States handling + + private func onInitialState() { + // + } + + private func onLoadingState(afterState: LoadingState) { + if case .initial = afterState { + wrappedView.scrollView.isUserInteractionEnabled = false + + removeCurrentPlaceholderView() + + guard let loadingIndicator = delegate?.initialLoadingIndicator() else { + return + } + + let loadingIndicatorView = loadingIndicator.view + + loadingIndicatorView.translatesAutoresizingMaskIntoConstraints = true + + wrappedView.backgroundView = loadingIndicatorView + + loadingIndicator.startAnimating() + + currentPlaceholderView = loadingIndicatorView + } else { + removeInfiniteScroll() + wrappedView.footerView = nil + } + } + + private func onLoadingMoreState(afterState: LoadingState) { + if case .error = afterState { // user tap retry button in table footer + delegate?.footerRetryButtonWillDisappear() + wrappedView.footerView = nil + addInfiniteScroll(withHandler: false) + wrappedView.scrollView.beginInfiniteScroll(true) + } + } + + private func onResultsState(newItems: DataLoadingModel.ResultType, + from cursor: Cursor, + afterState: LoadingState) { + + wrappedView.scrollView.isUserInteractionEnabled = true + + if case .initialLoading = afterState { + delegate?.paginationWrapper(didReload: newItems, using: cursor) + + removeCurrentPlaceholderView() + + wrappedView.scrollView.support.refreshControl?.endRefreshing() + + addInfiniteScroll(withHandler: true) + } else if case .loadingMore = afterState { + delegate?.paginationWrapper(didLoad: newItems, using: cursor) + + readdInfiniteScrollWithHandler() + } + } + + private func onErrorState(error: Error, afterState: LoadingState) { + if case .initialLoading = afterState { + defer { + wrappedView.scrollView.support.refreshControl?.endRefreshing() + } + + let customErrorHandling = delegate?.customInitialLoadingErrorHandling(for: error) ?? false + + guard !customErrorHandling, let errorView = delegate?.errorPlaceholder(for: error) else { + return + } + + replacePlaceholderViewIfNeeded(with: errorView) + + delegate?.clearView() + } else if case .loadingMore = afterState { + removeInfiniteScroll() + + guard let retryButton = delegate?.footerRetryButton(), + let retryButtonHeigth = delegate?.footerRetryButtonHeight() else { + return + } + + retryButton.frame = CGRect(x: 0, y: 0, width: wrappedView.scrollView.bounds.width, height: retryButtonHeigth) + + retryButton.rx + .controlEvent(.touchUpInside) + .asDriver() + .drive(reloadEvent) + .disposed(by: disposeBag) + + delegate?.footerRetryButtonWillAppear() + + wrappedView.footerView = retryButton + } + } + + private func onEmptyState() { + defer { + wrappedView.scrollView.support.refreshControl?.endRefreshing() + } + + delegate?.clearView() + + guard let emptyView = delegate?.emptyPlaceholder() else { + return + } + + replacePlaceholderViewIfNeeded(with: emptyView) + } + + private func replacePlaceholderViewIfNeeded(with placeholderView: UIView) { + wrappedView.scrollView.isUserInteractionEnabled = true + removeCurrentPlaceholderView() + + placeholderView.translatesAutoresizingMaskIntoConstraints = false + placeholderView.isHidden = false + + // I was unable to add pull-to-refresh placeholder scroll behaviour without this trick + let placeholderWrapperView = UIView() + placeholderWrapperView.addSubview(placeholderView) + + let leadingConstraint = placeholderView.leadingAnchor.constraint(equalTo: placeholderWrapperView.leadingAnchor) + let trailingConstraint = placeholderView.trailingAnchor.constraint(equalTo: placeholderWrapperView.trailingAnchor) + let topConstraint = placeholderView.topAnchor.constraint(equalTo: placeholderWrapperView.topAnchor) + let bottomConstraint = placeholderView.bottomAnchor.constraint(equalTo: placeholderWrapperView.bottomAnchor) + + NSLayoutConstraint.activate([leadingConstraint, + trailingConstraint, + topConstraint, + bottomConstraint]) + + currentPlaceholderViewTopConstraint = topConstraint + + wrappedView.backgroundView = placeholderWrapperView + + currentPlaceholderView = placeholderView + } + + // MARK: - private stuff + + private func onExhaustedState() { + removeInfiniteScroll() + } + + private func readdInfiniteScrollWithHandler() { + removeInfiniteScroll() + addInfiniteScroll(withHandler: true) + } + + private func addInfiniteScroll(withHandler: Bool) { + if withHandler { + wrappedView.scrollView.addInfiniteScroll { [weak paginationViewModel] _ in + paginationViewModel?.loadMore() + } + } else { + wrappedView.scrollView.addInfiniteScroll { _ in } + } + + wrappedView.scrollView.infiniteScrollIndicatorView = delegate?.loadingMoreIndicator().view + } + + private func removeInfiniteScroll() { + wrappedView.scrollView.finishInfiniteScroll() + wrappedView.scrollView.removeInfiniteScroll() + } + + private func createRefreshControl() { + let refreshControl = UIRefreshControl() + refreshControl.rx + .controlEvent(.valueChanged) + .asDriver() + .drive(reloadEvent) + .disposed(by: disposeBag) + + wrappedView.scrollView.support.setRefreshControl(refreshControl) + } + + private func removeRefreshControl() { + wrappedView.scrollView.support.setRefreshControl(nil) + } + + private func bindViewModelStates() { + paginationViewModel.stateDriver + .flatMap { [applicationCurrentyActive] state -> Driver in + if applicationCurrentyActive.value { + return .just(state) + } else { + return applicationCurrentyActive + .asObservable() + .filter { $0 } + .delay(0.5, scheduler: MainScheduler.instance) + .asDriver(onErrorJustReturn: true) + .map { _ in state } + } + } + .drive(stateChanged) + .disposed(by: disposeBag) + } + + private func removeCurrentPlaceholderView() { + wrappedView.backgroundView = nil + } + + private func bindAppStateNotifications() { + let notificationCenter = NotificationCenter.default.rx + + notificationCenter.notification(.UIApplicationWillResignActive) + .map { _ in false } + .asDriver(onErrorJustReturn: false) + .drive(applicationCurrentyActive) + .disposed(by: disposeBag) + + notificationCenter.notification(.UIApplicationDidBecomeActive) + .map { _ in true } + .asDriver(onErrorJustReturn: true) + .drive(applicationCurrentyActive) + .disposed(by: disposeBag) + } + +} + +private extension PaginationWrapper { + + private var stateChanged: Binder { + return Binder(self) { base, value in + switch value { + case .initial: + base.onInitialState() + case .initialLoading(let after): + base.onLoadingState(afterState: after) + case .loadingMore(let after): + base.onLoadingMoreState(afterState: after) + case .results(let newItems, let from, let after): + base.onResultsState(newItems: newItems, from: from, afterState: after) + case .error(let error, let after): + base.onErrorState(error: error, afterState: after) + case .empty: + base.onEmptyState() + case .exhausted: + base.onExhaustedState() + } + } + } + + var retryEvent: Binder<()> { + return Binder(self) { base, _ in + base.paginationViewModel.loadMore() + } + } + + var reloadEvent: Binder<()> { + return Binder(self) { base, _ in + base.reload() + } + } + + var scrollOffsetChanged: Binder { + return Binder(self) { base, value in + base.currentPlaceholderViewTopConstraint?.constant = -value.y + } + } + +} diff --git a/Sources/Classes/DataLoading/RxDataLoadingModel.swift b/Sources/Classes/DataLoading/RxDataLoadingModel.swift new file mode 100644 index 00000000..29372a7a --- /dev/null +++ b/Sources/Classes/DataLoading/RxDataLoadingModel.swift @@ -0,0 +1,108 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import RxSwift +import RxCocoa + +open class RxDataLoadingModel: DataLoadingModel + where LoadingStateType.DataSourceType: RxDataSource { + + public typealias DataSourceType = LoadingStateType.DataSourceType + public typealias ResultType = DataSourceType.ResultType + + public typealias EmptyResultChecker = (ResultType) -> Bool + + private let stateVariable = Variable(.initialState) + var currentRequestDisposable: Disposable? + + var dataSource: DataSourceType + let emptyResultChecker: EmptyResultChecker + + open var stateDriver: Driver { + return stateVariable.asDriver() + } + + public init(dataSource: DataSourceType, emptyResultChecker: @escaping EmptyResultChecker) { + self.dataSource = dataSource + self.emptyResultChecker = emptyResultChecker + } + + open func reload() { + load(isRetry: false) + } + + open func retry() { + load(isRetry: true) + } + + private func load(isRetry: Bool) { + currentRequestDisposable?.dispose() + + if isRetry { + state = .initialState + } + + state = .initialLoadingState(after: state) + + requestResult(from: dataSource) + } + + func onGot(error: Error) { + state = .errorState(error: error, after: state) + } + + private func onGot(result: ResultType, from dataSource: DataSourceType) { + if emptyResultChecker(result) { + state = .emptyState + } else { + state = .resultState(result: result, + from: dataSource, + after: state) + + updateStateAfterNonEmptyResult(from: dataSource) + } + } + + func requestResult(from dataSource: DataSourceType) { + currentRequestDisposable = dataSource + .resultSingle() + .subscribe(onSuccess: { [weak self] result in + self?.onGot(result: result, from: dataSource) + }, onError: { [weak self] error in + self?.onGot(error: error) + }) + } + + func updateStateAfterNonEmptyResult(from dataSource: DataSourceType) { + // override in subcass if needed + } + + var state: LoadingStateType { + get { + return stateVariable.value + } + set { + stateVariable.value = newValue + } + } + +} diff --git a/Sources/Classes/Pagination/PaginationTableViewWrapper.swift b/Sources/Classes/Pagination/PaginationTableViewWrapper.swift deleted file mode 100644 index edd69d85..00000000 --- a/Sources/Classes/Pagination/PaginationTableViewWrapper.swift +++ /dev/null @@ -1,378 +0,0 @@ -// -// Copyright (c) 2017 Touch Instinct -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import UIKit -import RxSwift -import RxCocoa -import UIScrollView_InfiniteScroll - -/// PaginationTableViewWrapper delegate used for pagination results handling and -/// customization of bound states (loading, empty, error, etc.). -public protocol PaginationTableViewWrapperDelegate: class { - - associatedtype Cursor: ResettableCursorType - - /// Delegate method that handles loading new chunk of data. - /// - /// - Parameters: - /// - wrapper: Wrapper object that loaded new items. - /// - newItems: New items. - /// - cursor: Cursor used to load items - func paginationWrapper(wrapper: PaginationTableViewWrapper, - didLoad newItems: [Cursor.Element], - usingCursor cursor: Cursor) - - /// Delegate method that handles reloading or initial loading of data. - /// - /// - Parameters: - /// - wrapper: Wrapper object that reload items. - /// - allItems: New items. - /// - cursor: Cursor used to load items - func paginationWrapper(wrapper: PaginationTableViewWrapper, - didReload allItems: [Cursor.Element], - usingCursor cursor: Cursor) - - /// Delegate method that returns placeholder view for empty state. - /// - /// - Parameter wrapper: Wrapper object that requests empty placeholder view. - /// - Returns: Configured instace of UIView. - func emptyPlaceholder(forPaginationWrapper wrapper: PaginationTableViewWrapper) -> UIView - - /// Delegate method that returns placeholder view for error state. - /// - /// - Parameters: - /// - wrapper: Wrapper object that requests error placeholder view. - /// - error: Error that occured due data loading. - /// - Returns: Configured instace of UIView. - func errorPlaceholder(forPaginationWrapper wrapper: PaginationTableViewWrapper, - forError error: Error) -> UIView - - /// Delegate method that returns loading idicator for initial loading state. - /// This indicator will appear at center of the placeholders container. - /// - /// - Parameter wrapper: Wrapper object that requests loading indicator - /// - Returns: Configured instace of AnyLoadingIndicator. - func initialLoadingIndicator(forPaginationWrapper wrapper: PaginationTableViewWrapper) -> AnyLoadingIndicator - - /// Delegate method that returns loading idicator for initial loading state. - /// - /// - Parameter wrapper: Wrapper object that requests loading indicator. - /// - Returns: Configured instace of AnyLoadingIndicator. - func loadingMoreIndicator(forPaginationWrapper wrapper: PaginationTableViewWrapper) -> AnyLoadingIndicator - - /// Delegate method that returns instance of UIButton for "retry load more" action. - /// - /// - Parameter wrapper: Wrapper object that requests button for "retry load more" action. - /// - Returns: Configured instace of AnyLoadingIndicator. - func retryLoadMoreButton(forPaginationWrapper wrapper: PaginationTableViewWrapper) -> UIButton - - /// Delegate method that returns preferred height for "retry load more" button. - /// - /// - Parameter wrapper: Wrapper object that requests height "retry load more" button. - /// - Returns: Preferred height of "retry load more" button. - func retryLoadMoreButtonHeight(forPaginationWrapper wrapper: PaginationTableViewWrapper) -> CGFloat - - // Delegate method, used to clear tableView if placeholder is shown. - func clearTableView() -} - -/// Class that connects PaginationViewModel with UITableView. It handles all non-visual and visual states. -final public class PaginationTableViewWrapper -where Delegate.Cursor == Cursor { - - private let tableView: UITableView - private let paginationViewModel: PaginationViewModel - private weak var delegate: Delegate? - - /// Sets the offset between the real end of the scroll view content and the scroll position, - /// so the handler can be triggered before reaching end. Defaults to 0.0; - public var infiniteScrollTriggerOffset: CGFloat { - get { - return tableView.infiniteScrollTriggerOffset - } - set { - tableView.infiniteScrollTriggerOffset = newValue - } - } - - private let disposeBag = DisposeBag() - - private var currentPlaceholderView: UIView? - private var currentPlaceholderViewTopConstraint: NSLayoutConstraint? - - private let applicationCurrentyActive = Variable(true) - - /// Initializer with table view, placeholders container view, cusor and delegate parameters. - /// - /// - Parameters: - /// - tableView: UITableView instance to work with. - /// - cursor: Cursor object that acts as data source. - /// - delegate: Delegate object for data loading events handling and UI customization. - public init(tableView: UITableView, cursor: Cursor, delegate: Delegate) { - self.tableView = tableView - self.paginationViewModel = PaginationViewModel(cursor: cursor) - self.delegate = delegate - - bindViewModelStates() - - createRefreshControl() - - bindAppStateNotifications() - } - - /// Method that reload all data in internal view model. - public func reload() { - paginationViewModel.load(.reload) - } - - /// Method acts like reload, but shows initial loading view after being invoked. - public func retry() { - paginationViewModel.load(.retry) - } - - /// Method that enables placeholders animation due pull-to-refresh interaction. - /// - /// - Parameter scrollObservable: Observable that emits content offset as CGPoint. - public func setScrollObservable(_ scrollObservable: Observable) { - scrollObservable.subscribe(onNext: { [weak self] offset in - self?.currentPlaceholderViewTopConstraint?.constant = -offset.y - }) - .disposed(by: disposeBag) - } - - // MARK: - States handling - - private func onInitialState() { - // - } - - private func onLoadingState(afterState: PaginationViewModel.State) { - if case .initial = afterState { - tableView.isUserInteractionEnabled = false - - removeCurrentPlaceholderView() - - guard let loadingIndicator = delegate?.initialLoadingIndicator(forPaginationWrapper: self) else { - return - } - - let loadingIndicatorView = loadingIndicator.view - - loadingIndicatorView.translatesAutoresizingMaskIntoConstraints = true - - tableView.backgroundView = loadingIndicatorView - - loadingIndicator.startAnimating() - - currentPlaceholderView = loadingIndicatorView - } else { - removeInfiniteScroll() - tableView.tableFooterView = nil - } - } - - private func onLoadingMoreState(afterState: PaginationViewModel.State) { - if case .error = afterState { // user tap retry button in table footer - tableView.tableFooterView = nil - addInfiniteScroll() - tableView.beginInfiniteScroll(true) - } - } - - private func onResultsState(newItems: [Cursor.Element], - inCursor cursor: Cursor, - afterState: PaginationViewModel.State) { - - tableView.isUserInteractionEnabled = true - - if case .loading = afterState { - delegate?.paginationWrapper(wrapper: self, didReload: newItems, usingCursor: cursor) - - removeCurrentPlaceholderView() - - tableView.support.refreshControl?.endRefreshing() - - if !cursor.exhausted { - addInfiniteScroll() - } - } else if case .loadingMore = afterState { - delegate?.paginationWrapper(wrapper: self, didLoad: newItems, usingCursor: cursor) - - tableView.finishInfiniteScroll() - } - } - - private func onErrorState(error: Error, afterState: PaginationViewModel.State) { - if case .loading = afterState { - defer { - tableView.support.refreshControl?.endRefreshing() - } - - guard let errorView = delegate?.errorPlaceholder(forPaginationWrapper: self, forError: error) else { - return - } - - replacePlaceholderViewIfNeeded(with: errorView) - } else if case .loadingMore = afterState { - removeInfiniteScroll() - - guard let retryButton = delegate?.retryLoadMoreButton(forPaginationWrapper: self), - let retryButtonHeigth = delegate?.retryLoadMoreButtonHeight(forPaginationWrapper: self) else { - return - } - - retryButton.frame = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: retryButtonHeigth) - - retryButton.rx.controlEvent(.touchUpInside) - .bind { [weak self] in - self?.paginationViewModel.load(.next) - } - .disposed(by: disposeBag) - - tableView.tableFooterView = retryButton - } - } - - private func onEmptyState() { - defer { - tableView.support.refreshControl?.endRefreshing() - } - guard let emptyView = delegate?.emptyPlaceholder(forPaginationWrapper: self) else { - return - } - replacePlaceholderViewIfNeeded(with: emptyView) - } - - private func replacePlaceholderViewIfNeeded(with placeholderView: UIView) { - tableView.isUserInteractionEnabled = true - removeCurrentPlaceholderView() - - placeholderView.translatesAutoresizingMaskIntoConstraints = false - placeholderView.isHidden = false - - // I was unable to add pull-to-refresh placeholder scroll behaviour without this trick - let wrapperView = UIView() - wrapperView.addSubview(placeholderView) - - let leadingConstraint = placeholderView.leadingAnchor.constraint(equalTo: wrapperView.leadingAnchor) - let trailingConstraint = placeholderView.trailingAnchor.constraint(equalTo: wrapperView.trailingAnchor) - let topConstraint = placeholderView.topAnchor.constraint(equalTo: wrapperView.topAnchor) - let bottomConstraint = placeholderView.bottomAnchor.constraint(equalTo: wrapperView.bottomAnchor) - - wrapperView.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint]) - - currentPlaceholderViewTopConstraint = topConstraint - - tableView.backgroundView = wrapperView - - currentPlaceholderView = placeholderView - } - - // MARK: - private stuff - - private func onExhaustedState() { - removeInfiniteScroll() - } - - private func addInfiniteScroll() { - tableView.addInfiniteScroll { [weak paginationViewModel] _ in - paginationViewModel?.load(.next) - } - - tableView.infiniteScrollIndicatorView = delegate?.loadingMoreIndicator(forPaginationWrapper: self).view - } - - private func removeInfiniteScroll() { - tableView.finishInfiniteScroll() - tableView.removeInfiniteScroll() - } - - private func createRefreshControl() { - let refreshControl = UIRefreshControl() - refreshControl.rx.controlEvent(.valueChanged) - .bind { [weak self] in - self?.reload() - } - .disposed(by: disposeBag) - - tableView.support.setRefreshControl(refreshControl) - } - - private func bindViewModelStates() { - typealias State = PaginationViewModel.State - - paginationViewModel.state.flatMap { [applicationCurrentyActive] state -> Driver in - if applicationCurrentyActive.value { - return .just(state) - } else { - return applicationCurrentyActive - .asObservable() - .filter { $0 } - .delay(0.5, scheduler: MainScheduler.instance) - .asDriver(onErrorJustReturn: true) - .map { _ in state } - } - } - .drive(onNext: { [weak self] state in - switch state { - case .initial: - self?.onInitialState() - case .loading(let after): - self?.onLoadingState(afterState: after) - case .loadingMore(let after): - self?.onLoadingMoreState(afterState: after) - case .results(let newItems, let cursor, let after): - self?.onResultsState(newItems: newItems, inCursor: cursor, afterState: after) - case .error(let error, let after): - self?.delegate?.clearTableView() - self?.onErrorState(error: error, afterState: after) - case .empty: - self?.delegate?.clearTableView() - self?.onEmptyState() - case .exhausted: - self?.onExhaustedState() - } - }) - .disposed(by: disposeBag) - } - - private func removeCurrentPlaceholderView() { - tableView.backgroundView = nil - } - - private func bindAppStateNotifications() { - let notificationCenter = NotificationCenter.default.rx - - notificationCenter.notification(.UIApplicationWillResignActive) - .subscribe(onNext: { [weak self] _ in - self?.applicationCurrentyActive.value = false - }) - .disposed(by: disposeBag) - - notificationCenter.notification(.UIApplicationDidBecomeActive) - .subscribe(onNext: { [weak self] _ in - self?.applicationCurrentyActive.value = true - }) - .disposed(by: disposeBag) - } - -} diff --git a/Sources/Classes/Pagination/PaginationViewModel.swift b/Sources/Classes/Pagination/PaginationViewModel.swift deleted file mode 100644 index 888ccfec..00000000 --- a/Sources/Classes/Pagination/PaginationViewModel.swift +++ /dev/null @@ -1,154 +0,0 @@ -// -// Copyright (c) 2017 Touch Instinct -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import RxSwift -import RxCocoa - -/// Class that encapsulate all pagination logic -public final class PaginationViewModel { - - /// Enum contains all possible states for PaginationViewModel class. - /// - /// - initial: initial state of view model. - /// Can occur only once after initial binding. - /// - loading: loading state of view model. Contains previous state of view model. - /// Can occur after any state. - /// - loadingMore: loading more items state of view model. Contains previous state of view model. - /// Can occur after error or results state. - /// - results: results state of view model. Contains loaded items, cursor and previous state of view model. - /// Can occur after loading or loadingMore state. - /// - error: error state of view model. Contains received error and previous state of view model. - /// Can occur after loading or loadingMore state. - /// - empty: empty state of view model. - /// Can occur after loading or loadingMore state when we got empty result (zero items). - /// - exhausted: exhausted state of view model. - /// Can occur after results state or after initial->loading state when cursor reports that it's exhausted. - public indirect enum State { - - case initial - case loading(after: State) - case loadingMore(after: State) - case results(newItems: [C.Element], inCursor: C, after: State) - case error(error: Error, after: State) - case empty - case exhausted - - } - - /// Enum represents possible load types for PaginationViewModel class - /// - /// - reload: reload all items and reset cursor to initial state. - /// - next: load next batch of items. - /// - retry: reload to initial loading state - public enum LoadType { - - case reload - case retry - case next - - } - - private var cursor: C - - private let internalState = Variable(.initial) - - private var currentRequest: Disposable? - - private let internalScheduler = SerialDispatchQueueScheduler(qos: .default) - - /// Current PaginationViewModel state Driver - public var state: Driver { - return internalState.asDriver() - } - - /// Initializer with enclosed cursor - /// - /// - Parameter cursor: cursor to use for pagination - public init(cursor: C) { - self.cursor = cursor - } - - /// Mathod which triggers loading of items. - /// - /// - Parameter loadType: type of loading. See LoadType enum. - public func load(_ loadType: LoadType) { - switch loadType { - case .reload: - reload() - case .retry: - reload(isRetry: true) - case .next: - if case .exhausted = internalState.value { - fatalError("You shouldn't call load(.next) after got .exhausted state!") - } - - internalState.value = .loadingMore(after: internalState.value) - } - - let currentCursor = cursor - - currentRequest = currentCursor.loadNextBatch() - .subscribeOn(internalScheduler) - .subscribe(onSuccess: { [weak self] newItems in - self?.onGot(newItems: newItems, using: currentCursor) - }, onError: { [weak self] error in - self?.onGot(error: error) - }) - } - - private func onGot(newItems: [C.Element], using cursor: C) { - if newItems.isEmpty { - internalState.value = .empty - return - } - - internalState.value = .results(newItems: newItems, inCursor: cursor, after: internalState.value) - - if cursor.exhausted { - internalState.value = .exhausted - } - } - - private func onGot(error: Error) { - if case .exhausted? = error as? CursorError, case .loading(let after) = internalState.value { - switch after { - case .initial, .empty: // cursor exhausted after creation - internalState.value = .empty - default: - internalState.value = .error(error: error, after: internalState.value) - } - } else { - internalState.value = .error(error: error, after: internalState.value) - } - } - - private func reload(isRetry: Bool = false) { - currentRequest?.dispose() - cursor = cursor.reset() - - if isRetry { - internalState.value = .initial - } - internalState.value = .loading(after: internalState.value) - } - -} diff --git a/Sources/Enums/CursorError.swift b/Sources/Enums/DataLoading/CursorError.swift similarity index 100% rename from Sources/Enums/CursorError.swift rename to Sources/Enums/DataLoading/CursorError.swift diff --git a/Sources/Enums/DataLoading/GeneralDataLoading/GeneralDataLoadingState.swift b/Sources/Enums/DataLoading/GeneralDataLoading/GeneralDataLoadingState.swift new file mode 100644 index 00000000..0b550995 --- /dev/null +++ b/Sources/Enums/DataLoading/GeneralDataLoading/GeneralDataLoadingState.swift @@ -0,0 +1,58 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public enum GeneralDataLoadingState: DataLoadingState { + + case initial + case loading + case result(newResult: DS.ResultType, from: DS) + case error(error: Error) + case empty + + public typealias DataSourceType = DS + + public static var initialState: GeneralDataLoadingState { + return .initial + } + + public static var emptyState: GeneralDataLoadingState { + return .empty + } + + public static func initialLoadingState(after: GeneralDataLoadingState) -> GeneralDataLoadingState { + return .loading + } + + public static func resultState(result: DS.ResultType, + from: DS, + after: GeneralDataLoadingState) -> GeneralDataLoadingState { + + return .result(newResult: result, from: from) + } + + public static func errorState(error: Error, + after: GeneralDataLoadingState) -> GeneralDataLoadingState { + + return .error(error: error) + } + +} diff --git a/Sources/Enums/DataLoading/PaginationDataLoading/PaginationDataLoadingState.swift b/Sources/Enums/DataLoading/PaginationDataLoading/PaginationDataLoadingState.swift new file mode 100644 index 00000000..a995f774 --- /dev/null +++ b/Sources/Enums/DataLoading/PaginationDataLoading/PaginationDataLoadingState.swift @@ -0,0 +1,60 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public indirect enum PaginationDataLoadingState: DataLoadingState { + + case initial + case initialLoading(after: PaginationDataLoadingState) + case loadingMore(after: PaginationDataLoadingState) + case results(newItems: DS.ResultType, from: DS, after: PaginationDataLoadingState) + case error(error: Error, after: PaginationDataLoadingState) + case empty + case exhausted + + public typealias DataSourceType = DS + + public static var initialState: PaginationDataLoadingState { + return .initial + } + + public static var emptyState: PaginationDataLoadingState { + return .empty + } + + public static func initialLoadingState(after: PaginationDataLoadingState) -> PaginationDataLoadingState { + return .initialLoading(after: after) + } + + public static func resultState(result: DS.ResultType, + from: DataSourceType, + after: PaginationDataLoadingState) -> PaginationDataLoadingState { + + return .results(newItems: result, from: from, after: after) + } + + public static func errorState(error: Error, + after: PaginationDataLoadingState) -> PaginationDataLoadingState { + + return .error(error: error, after: after) + } + +} diff --git a/Sources/Extensions/DataLoading/CursorType/CursorType+RxDataSourceDefaultImplementation.swift b/Sources/Extensions/DataLoading/CursorType/CursorType+RxDataSourceDefaultImplementation.swift new file mode 100644 index 00000000..79075ada --- /dev/null +++ b/Sources/Extensions/DataLoading/CursorType/CursorType+RxDataSourceDefaultImplementation.swift @@ -0,0 +1,33 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import RxSwift + +public extension CursorType { + + typealias ResultType = [Element] + + func resultSingle() -> Single { + return loadNextBatch() + } + +} diff --git a/Sources/Extensions/CursorType/CursorType+Slice.swift b/Sources/Extensions/DataLoading/CursorType/CursorType+Slice.swift similarity index 100% rename from Sources/Extensions/CursorType/CursorType+Slice.swift rename to Sources/Extensions/DataLoading/CursorType/CursorType+Slice.swift diff --git a/Sources/Extensions/Cursors/TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift b/Sources/Extensions/DataLoading/Cursors/TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift similarity index 100% rename from Sources/Extensions/Cursors/TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift rename to Sources/Extensions/DataLoading/Cursors/TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift diff --git a/Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift b/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift similarity index 63% rename from Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift rename to Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift index 7dd721ad..6b5c5ac4 100644 --- a/Sources/Extensions/PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift +++ b/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Touch Instinct +// Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal @@ -22,9 +22,9 @@ import UIKit -public extension PaginationTableViewWrapperDelegate { +public extension PaginationWrapperDelegate { - func emptyPlaceholder(forPaginationWrapper wrapper: PaginationTableViewWrapper) -> UIView { + func emptyPlaceholder() -> UIView { let placeholder = UIView() let label = UILabel() @@ -32,15 +32,20 @@ public extension PaginationTableViewWrapperDelegate { label.text = "There is nothing here" placeholder.addSubview(label) - label.centerXAnchor.constraint(equalTo: placeholder.centerXAnchor).isActive = true - label.centerYAnchor.constraint(equalTo: placeholder.centerYAnchor).isActive = true + + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: placeholder.centerXAnchor), + label.centerYAnchor.constraint(equalTo: placeholder.centerYAnchor) + ]) return placeholder } - func errorPlaceholder(forPaginationWrapper wrapper: PaginationTableViewWrapper, - forError error: Error) -> UIView { + func customInitialLoadingErrorHandling(for error: Error) -> Bool { + return false + } + func errorPlaceholder(for error: Error) -> UIView { let placeholder = UIView() let label = UILabel() @@ -48,30 +53,29 @@ public extension PaginationTableViewWrapperDelegate { label.text = "An error has occurred" placeholder.addSubview(label) - label.centerXAnchor.constraint(equalTo: placeholder.centerXAnchor).isActive = true - label.centerYAnchor.constraint(equalTo: placeholder.centerYAnchor).isActive = true + + NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: placeholder.centerXAnchor), + label.centerYAnchor.constraint(equalTo: placeholder.centerYAnchor) + ]) return placeholder } - func initialLoadingIndicator(forPaginationWrapper wrapper: PaginationTableViewWrapper) - -> AnyLoadingIndicator { - + func initialLoadingIndicator() -> AnyLoadingIndicator { let indicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) indicator.color = .gray return AnyLoadingIndicator(indicator) } - func loadingMoreIndicator(forPaginationWrapper wrapper: PaginationTableViewWrapper) - -> AnyLoadingIndicator { - + func loadingMoreIndicator() -> AnyLoadingIndicator { let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) return AnyLoadingIndicator(indicator) } - func retryLoadMoreButton(forPaginationWrapper wrapper: PaginationTableViewWrapper) -> UIButton { + func footerRetryButton() -> UIButton { let retryButton = UIButton(type: .custom) retryButton.backgroundColor = .lightGray retryButton.setTitle("Retry load more", for: .normal) @@ -79,8 +83,16 @@ public extension PaginationTableViewWrapperDelegate { return retryButton } - func retryLoadMoreButtonHeight(forPaginationWrapper wrapper: PaginationTableViewWrapper) -> CGFloat { + func footerRetryButtonHeight() -> CGFloat { return 44 } + func footerRetryButtonWillAppear() { + // by default - nothing will happen + } + + func footerRetryButtonWillDisappear() { + // by default - nothing will happen + } + } diff --git a/Sources/Extensions/DataLoading/PaginationDataLoading/UICollectionView+PaginationWrappable.swift b/Sources/Extensions/DataLoading/PaginationDataLoading/UICollectionView+PaginationWrappable.swift new file mode 100644 index 00000000..732b7abf --- /dev/null +++ b/Sources/Extensions/DataLoading/PaginationDataLoading/UICollectionView+PaginationWrappable.swift @@ -0,0 +1,40 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UICollectionView + +extension UICollectionView: PaginationWrappable { + + public var scrollView: UIScrollView { + return self + } + + public var footerView: UIView? { + get { + return nil + } + set { + // nothing + } + } + +} diff --git a/Sources/Extensions/DataLoading/PaginationDataLoading/UITableView+PaginationWrappable.swift b/Sources/Extensions/DataLoading/PaginationDataLoading/UITableView+PaginationWrappable.swift new file mode 100644 index 00000000..0915e3c5 --- /dev/null +++ b/Sources/Extensions/DataLoading/PaginationDataLoading/UITableView+PaginationWrappable.swift @@ -0,0 +1,40 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UITableView + +extension UITableView: PaginationWrappable { + + public var scrollView: UIScrollView { + return self + } + + public var footerView: UIView? { + get { + return tableFooterView + } + set { + tableFooterView = newValue + } + } + +} diff --git a/Sources/Extensions/DataLoading/Rx+RxDataSourceProtocol.swift b/Sources/Extensions/DataLoading/Rx+RxDataSourceProtocol.swift new file mode 100644 index 00000000..f886e155 --- /dev/null +++ b/Sources/Extensions/DataLoading/Rx+RxDataSourceProtocol.swift @@ -0,0 +1,48 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import RxSwift +import RxCocoa + +extension Observable: RxDataSource { + + public typealias ResultType = Element + + public func resultSingle() -> Single { + return asSingle() + } + +} + +// waiting for Swift 4.2 release... +// https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md +// extension PrimitiveSequence: RxDataSource where Trait == SingleTrait { + +extension Single: RxDataSource { + + public typealias ResultType = Element + + public func resultSingle() -> Single { + return asObservable().asSingle() + } + +} diff --git a/Sources/Protocols/CursorType.swift b/Sources/Protocols/DataLoading/Cursors/CursorType.swift similarity index 100% rename from Sources/Protocols/CursorType.swift rename to Sources/Protocols/DataLoading/Cursors/CursorType.swift diff --git a/Sources/Protocols/Cursors/ResettableCursorType.swift b/Sources/Protocols/DataLoading/Cursors/ResettableCursorType.swift similarity index 100% rename from Sources/Protocols/Cursors/ResettableCursorType.swift rename to Sources/Protocols/DataLoading/Cursors/ResettableCursorType.swift diff --git a/Sources/Protocols/Cursors/ResettableCursorDataSource.swift b/Sources/Protocols/DataLoading/Cursors/ResettableRxCursorDataSource.swift similarity index 93% rename from Sources/Protocols/Cursors/ResettableCursorDataSource.swift rename to Sources/Protocols/DataLoading/Cursors/ResettableRxCursorDataSource.swift index c64be204..b05c835f 100644 --- a/Sources/Protocols/Cursors/ResettableCursorDataSource.swift +++ b/Sources/Protocols/DataLoading/Cursors/ResettableRxCursorDataSource.swift @@ -21,4 +21,4 @@ // /// Ressetable cursor type that conforms to DataSourceProtocol -public typealias ResettableCursorDataSource = ResettableCursorType & DataSourceProtocol +public typealias ResettableRxDataSourceCursor = ResettableCursorType & RxDataSource diff --git a/Sources/Protocols/DataLoading/DataLoadingModel.swift b/Sources/Protocols/DataLoading/DataLoadingModel.swift new file mode 100644 index 00000000..a55496ff --- /dev/null +++ b/Sources/Protocols/DataLoading/DataLoadingModel.swift @@ -0,0 +1,40 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import RxCocoa + +/// Protocol that describes data loading process +/// with methods reload & retry and current state driver variable. +public protocol DataLoadingModel { + + associatedtype LoadingStateType: DataLoadingState + + /// Driver, that emits current state of loading process + var stateDriver: Driver { get } + + /// Perform (re)load data. + func reload() + + /// Attempt to load data again. + func retry() + +} diff --git a/Sources/Protocols/DataLoading/DataLoadingState.swift b/Sources/Protocols/DataLoading/DataLoadingState.swift new file mode 100644 index 00000000..29a02f72 --- /dev/null +++ b/Sources/Protocols/DataLoading/DataLoadingState.swift @@ -0,0 +1,57 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// Protocol that describes possible states of data loading process. +public protocol DataLoadingState { + + associatedtype DataSourceType: DataSource + + /// Initial state. Before something will happen. + static var initialState: Self { get } + + /// Empty state. When data was requested and empty result was received. + static var emptyState: Self { get } + + /// Method that returns loading state after a given state. + /// + /// - Parameter after: Previous state of data loading process. + /// - Returns: Instance of loading state with given argument. + static func initialLoadingState(after: Self) -> Self + + /// Method that returns result state from a specific data source after a given state. + /// + /// - Parameters: + /// - result: DataSource result. + /// - from: DataSource from that a result was received. + /// - after: Previous state of data loading process. + /// - Returns: Instance of result state with given arguments. + static func resultState(result: DataSourceType.ResultType, from: DataSourceType, after: Self) -> Self + + /// Method that returns error state with a specific error after a given state. + /// + /// - Parameters: + /// - error: An error that happens due data loading process. + /// - after: Previous state of data loading process. + /// - Returns: Instance of error state with given arguments. + static func errorState(error: Error, after: Self) -> Self + +} diff --git a/Sources/Protocols/Loading/DataSourceProtocol.swift b/Sources/Protocols/DataLoading/DataSource.swift similarity index 97% rename from Sources/Protocols/Loading/DataSourceProtocol.swift rename to Sources/Protocols/DataLoading/DataSource.swift index 1d01807e..f1f45fd3 100644 --- a/Sources/Protocols/Loading/DataSourceProtocol.swift +++ b/Sources/Protocols/DataLoading/DataSource.swift @@ -21,7 +21,7 @@ // /// Protocol for data sources that has type of loaded result -public protocol DataSourceProtocol { +public protocol DataSource { associatedtype ResultType diff --git a/Sources/Protocols/DataLoading/PaginationDataLoading/PaginationWrappable.swift b/Sources/Protocols/DataLoading/PaginationDataLoading/PaginationWrappable.swift new file mode 100644 index 00000000..e68f85db --- /dev/null +++ b/Sources/Protocols/DataLoading/PaginationDataLoading/PaginationWrappable.swift @@ -0,0 +1,47 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +/// Protocol that contains scroll view property +public protocol ScrollViewHolder { + + var scrollView: UIScrollView { get } + +} + +/// Protocol that contains background view property +public protocol BackgroundViewHolder { + + var backgroundView: UIView? { get set } + +} + +/// Protocol that contains footer view property +public protocol FooterViewHolder { + + var footerView: UIView? { get set } + +} + +/// Protocol that conforms to ScrollViewHolder, BackgroundViewHolder and FooterViewHolder protocols +public typealias PaginationWrappable = ScrollViewHolder & BackgroundViewHolder & FooterViewHolder diff --git a/Sources/Protocols/Loading/Pagination/TotalCountCursorConfiguration.swift b/Sources/Protocols/DataLoading/PaginationDataLoading/TotalCountCursorConfiguration.swift similarity index 78% rename from Sources/Protocols/Loading/Pagination/TotalCountCursorConfiguration.swift rename to Sources/Protocols/DataLoading/PaginationDataLoading/TotalCountCursorConfiguration.swift index f2b17660..ec6923d9 100644 --- a/Sources/Protocols/Loading/Pagination/TotalCountCursorConfiguration.swift +++ b/Sources/Protocols/DataLoading/PaginationDataLoading/TotalCountCursorConfiguration.swift @@ -22,12 +22,9 @@ import RxSwift -/// Protocol that requests class to conform ResettableType -/// and return next batch of data on nextBatchObservable call -public protocol TotalCountCursorConfiguration: class, ResettableType { - - associatedtype ListingType: TotalCountCursorListingResult - - func nextBatchObservable() -> Single +/// Protocol that requests class to conform RxDataSource and ResettableType +/// with constraint ResultType to TotalCountCursorListingResult. +public protocol TotalCountCursorConfiguration: class, RxDataSource, ResettableType + where ResultType: TotalCountCursorListingResult { } diff --git a/Sources/Protocols/Loading/Pagination/TotalCountCursorListingResult.swift b/Sources/Protocols/DataLoading/PaginationDataLoading/TotalCountCursorListingResult.swift similarity index 100% rename from Sources/Protocols/Loading/Pagination/TotalCountCursorListingResult.swift rename to Sources/Protocols/DataLoading/PaginationDataLoading/TotalCountCursorListingResult.swift diff --git a/Sources/Protocols/DataLoading/RxDataSource.swift b/Sources/Protocols/DataLoading/RxDataSource.swift new file mode 100644 index 00000000..af111c14 --- /dev/null +++ b/Sources/Protocols/DataLoading/RxDataSource.swift @@ -0,0 +1,33 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import RxSwift + +/// Protocol with method for requesting data wrapped with Single. +public protocol RxDataSource: DataSource { + + /// Request data and returns Single of data type. + /// + /// - Returns: Single sequence with one element. + func resultSingle() -> Single + +} diff --git a/Sources/Structures/Cursors/DefaultTotalCountCursorListingResult.swift b/Sources/Structures/DataLoading/Cursors/DefaultTotalCountCursorListingResult.swift similarity index 100% rename from Sources/Structures/Cursors/DefaultTotalCountCursorListingResult.swift rename to Sources/Structures/DataLoading/Cursors/DefaultTotalCountCursorListingResult.swift diff --git a/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappable.swift b/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappable.swift new file mode 100644 index 00000000..a8ace596 --- /dev/null +++ b/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappable.swift @@ -0,0 +1,72 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +/// Type that performs some kind of type erasure for PaginationWrappable. +public final class AnyPaginationWrappable: PaginationWrappable { + + private typealias ViewSetter = (UIView?) -> Void + + public var footerView: UIView? { + get { + return footerViewBacking + } + set { + footerViewSetter(newValue) + } + } + + public var backgroundView: UIView? { + get { + return backgroundViewBacking + } + set { + backgroundViewSetter(newValue) + } + } + + public let scrollView: UIScrollView + + private let backgroundViewBacking: UIView? + private let footerViewBacking: UIView? + + private let backgroundViewSetter: ViewSetter + private let footerViewSetter: ViewSetter + + public init(view: View) where View: PaginationWrappable { + self.scrollView = view.scrollView + self.backgroundViewBacking = view.backgroundView + self.footerViewBacking = view.footerView + + var localView = view + + self.backgroundViewSetter = { + localView.backgroundView = $0 + } + + self.footerViewSetter = { + localView.footerView = $0 + } + } + +} diff --git a/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift b/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift new file mode 100644 index 00000000..97f181d6 --- /dev/null +++ b/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift @@ -0,0 +1,97 @@ +// +// Copyright (c) 2018 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// PaginationWrapper delegate used for pagination results handling and +/// customization of bound states (loading, empty, error, etc.). +public protocol PaginationWrapperDelegate: class { + + associatedtype DataSourceType: DataSource + + /// Handles loading new chunk of data. + /// + /// - Parameters: + /// - newItems: New items. + /// - dataSource: Data source used to load items + func paginationWrapper(didLoad newItems: DataSourceType.ResultType, + using dataSource: DataSourceType) + + /// Handles reloading or initial loading of data. + /// + /// - Parameters: + /// - allItems: New items. + /// - dataSource: Data source used to load items + func paginationWrapper(didReload allItems: DataSourceType.ResultType, + using dataSource: DataSourceType) + + /// Returns placeholder view for empty state. + /// + /// - Returns: Configured instace of UIView. + func emptyPlaceholder() -> UIView + + /// Called when initial loading error is occured. + /// It should return true if error is handled and false if it doesn't. + /// + /// - Parameters: + /// - error: Error that occured due data loading. + /// - Returns: Bool value. If true - then error placeholder wouldn't be shown. + func customInitialLoadingErrorHandling(for error: Error) -> Bool + + /// Returns placeholder view for error state. + /// + /// - Parameters: + /// - error: Error that occured due data loading. + /// - Returns: Configured instace of UIView. + func errorPlaceholder(for error: Error) -> UIView + + /// Returns loading idicator for initial loading state. + /// This indicator will appear at center of the placeholders container. + /// + /// - Returns: Configured instace of AnyLoadingIndicator. + func initialLoadingIndicator() -> AnyLoadingIndicator + + /// Returns loading idicator for initial loading state. + /// + /// - Returns: Configured instace of AnyLoadingIndicator. + func loadingMoreIndicator() -> AnyLoadingIndicator + + /// Returns instance of UIButton for "retry load more" action. + /// + /// - Returns: Configured instace of AnyLoadingIndicator. + func footerRetryButton() -> UIButton + + /// Returns height for "retry load more" button. + /// + /// - Returns: Height of "retry load more" button. + func footerRetryButtonHeight() -> CGFloat + + /// Method is called before "retry load more" will be shown. + /// Typically, it's used when you need to show custom footer view. + func footerRetryButtonWillAppear() + + /// Method is called before "retry load more" will be hidden. + /// Typically, it's used when you need to hide custom footer view. + func footerRetryButtonWillDisappear() + + /// Clears view when placeholder is shown. + func clearView() + +}