From 03619df2f1fd5babfaac77b4a155c40a7b68695e Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 21 Mar 2018 16:24:11 +0300 Subject: [PATCH 01/10] add data loading classes; update PaginationWrapper (collection view support) --- LeadKit.podspec | 42 +- LeadKit.xcodeproj/project.pbxproj | 320 ++++++++++++--- .../Cursors/FixedPageCursor.swift | 0 .../{ => DataLoading}/Cursors/MapCursor.swift | 0 .../Cursors/SingleLoadCursor.swift | 0 .../Cursors/StaticCursor.swift | 0 .../Cursors/TotalCountCursor.swift | 10 +- .../GeneralDataLoadingModel.swift | 26 ++ .../PaginationDataLoadingModel.swift | 92 +++++ .../PaginationViewModel.swift | 0 .../PaginationWrapper.swift | 339 ++++++++++++++++ .../DataLoading/RxDataLoadingModel.swift | 113 ++++++ .../PaginationTableViewWrapper.swift | 378 ------------------ .../Enums/{ => DataLoading}/CursorError.swift | 0 .../GeneralDataLoadingState.swift | 58 +++ .../PaginationDataLoadingState.swift | 60 +++ ...pe+RxDataSourceDefaultImplementation.swift | 33 ++ .../CursorType/CursorType+Slice.swift | 0 ...DefaultTotalCountCursorListingResult.swift | 0 ...apperDelegate+DefaultImplementation.swift} | 46 ++- ...UICollectionView+PaginationWrappable.swift | 40 ++ .../UITableView+PaginationWrappable.swift | 40 ++ .../DataLoading/Rx+RxDataSourceProtocol.swift | 48 +++ .../Cursors}/CursorType.swift | 0 .../Cursors/ResettableCursorType.swift | 0 .../ResettableRxCursorDataSource.swift} | 2 +- .../DataLoading/DataLoadingModel.swift | 40 ++ .../DataLoading/DataLoadingState.swift | 57 +++ .../DataSource.swift} | 2 +- .../PaginationWrappable.swift | 47 +++ .../TotalCountCursorConfiguration.swift | 11 +- .../TotalCountCursorListingResult.swift | 0 .../Protocols/DataLoading/RxDataSource.swift | 33 ++ ...DefaultTotalCountCursorListingResult.swift | 0 .../AnyPaginationWrappableView.swift | 72 ++++ .../PaginationWrapperDelegate.swift | 96 +++++ 36 files changed, 1523 insertions(+), 482 deletions(-) rename Sources/Classes/{ => DataLoading}/Cursors/FixedPageCursor.swift (100%) rename Sources/Classes/{ => DataLoading}/Cursors/MapCursor.swift (100%) rename Sources/Classes/{ => DataLoading}/Cursors/SingleLoadCursor.swift (100%) rename Sources/Classes/{ => DataLoading}/Cursors/StaticCursor.swift (100%) rename Sources/Classes/{ => DataLoading}/Cursors/TotalCountCursor.swift (89%) create mode 100644 Sources/Classes/DataLoading/GeneralDataLoading/GeneralDataLoadingModel.swift create mode 100644 Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift rename Sources/Classes/{Pagination => DataLoading/PaginationDataLoading}/PaginationViewModel.swift (100%) create mode 100644 Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift create mode 100644 Sources/Classes/DataLoading/RxDataLoadingModel.swift delete mode 100644 Sources/Classes/Pagination/PaginationTableViewWrapper.swift rename Sources/Enums/{ => DataLoading}/CursorError.swift (100%) create mode 100644 Sources/Enums/DataLoading/GeneralDataLoading/GeneralDataLoadingState.swift create mode 100644 Sources/Enums/DataLoading/PaginationDataLoading/PaginationDataLoadingState.swift create mode 100644 Sources/Extensions/DataLoading/CursorType/CursorType+RxDataSourceDefaultImplementation.swift rename Sources/Extensions/{ => DataLoading}/CursorType/CursorType+Slice.swift (100%) rename Sources/Extensions/{ => DataLoading}/Cursors/TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift (100%) rename Sources/Extensions/{PaginationTableViewWrapperDelegate/PaginationTableViewWrapperDelegate+DefaultImplementation.swift => DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift} (63%) create mode 100644 Sources/Extensions/DataLoading/PaginationDataLoading/UICollectionView+PaginationWrappable.swift create mode 100644 Sources/Extensions/DataLoading/PaginationDataLoading/UITableView+PaginationWrappable.swift create mode 100644 Sources/Extensions/DataLoading/Rx+RxDataSourceProtocol.swift rename Sources/Protocols/{ => DataLoading/Cursors}/CursorType.swift (100%) rename Sources/Protocols/{ => DataLoading}/Cursors/ResettableCursorType.swift (100%) rename Sources/Protocols/{Cursors/ResettableCursorDataSource.swift => DataLoading/Cursors/ResettableRxCursorDataSource.swift} (93%) create mode 100644 Sources/Protocols/DataLoading/DataLoadingModel.swift create mode 100644 Sources/Protocols/DataLoading/DataLoadingState.swift rename Sources/Protocols/{Loading/DataSourceProtocol.swift => DataLoading/DataSource.swift} (97%) create mode 100644 Sources/Protocols/DataLoading/PaginationDataLoading/PaginationWrappable.swift rename Sources/Protocols/{Loading/Pagination => DataLoading/PaginationDataLoading}/TotalCountCursorConfiguration.swift (78%) rename Sources/Protocols/{Loading/Pagination => DataLoading/PaginationDataLoading}/TotalCountCursorListingResult.swift (100%) create mode 100644 Sources/Protocols/DataLoading/RxDataSource.swift rename Sources/Structures/{ => DataLoading}/Cursors/DefaultTotalCountCursorListingResult.swift (100%) create mode 100644 Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappableView.swift create mode 100644 Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift 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..b6379277 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -41,7 +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 */; }; @@ -108,7 +107,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 +302,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 +385,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 +401,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 /* AnyPaginationWrappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappableView.swift */; }; + 67EB7FFE206176C900BDD9FB /* AnyPaginationWrappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappableView.swift */; }; + 67EB7FFF206176C900BDD9FB /* AnyPaginationWrappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappableView.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,7 +512,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 = ""; }; @@ -474,7 +529,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 +589,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 +623,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 /* AnyPaginationWrappableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyPaginationWrappableView.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 +737,8 @@ 671461C41EB3396E00EAB194 /* Classes */ = { isa = PBXGroup; children = ( - 671461C71EB3396E00EAB194 /* Cursors */, + 6774527E2062566D0024EEEF /* DataLoading */, 671461CB1EB3396E00EAB194 /* Logging */, - 671461CF1EB3396E00EAB194 /* Pagination */, 671461D21EB3396E00EAB194 /* Services */, 671461D41EB3396E00EAB194 /* Views */, ); @@ -697,13 +767,14 @@ path = Logging; sourceTree = ""; }; - 671461CF1EB3396E00EAB194 /* Pagination */ = { + 671461CF1EB3396E00EAB194 /* PaginationDataLoading */ = { isa = PBXGroup; children = ( - 671461D01EB3396E00EAB194 /* PaginationTableViewWrapper.swift */, + 6774529E20625EEE0024EEEF /* PaginationDataLoadingModel.swift */, 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */, + 677452AD206274630024EEEF /* PaginationWrapper.swift */, ); - path = Pagination; + path = PaginationDataLoading; sourceTree = ""; }; 671461D21EB3396E00EAB194 /* Services */ = { @@ -730,7 +801,7 @@ 671461D61EB3396E00EAB194 /* Enums */ = { isa = PBXGroup; children = ( - 671461D71EB3396E00EAB194 /* CursorError.swift */, + 6774528A20625C860024EEEF /* DataLoading */, 671461D81EB3396E00EAB194 /* LeadKitError.swift */, 671461D91EB3396E00EAB194 /* ResizeMode.swift */, 67FDC25E1FA310EA00C76A77 /* RequestError.swift */, @@ -749,14 +820,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 +891,7 @@ isa = PBXGroup; children = ( 671461E91EB3396E00EAB194 /* CursorType+Slice.swift */, + 677452A8206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift */, ); path = CursorType; sourceTree = ""; @@ -842,14 +912,6 @@ path = Observable; sourceTree = ""; }; - 671461F31EB3396E00EAB194 /* PaginationTableViewWrapperDelegate */ = { - isa = PBXGroup; - children = ( - 671461F41EB3396E00EAB194 /* PaginationTableViewWrapperDelegate+DefaultImplementation.swift */, - ); - path = PaginationTableViewWrapperDelegate; - sourceTree = ""; - }; 671461F51EB3396E00EAB194 /* Sequence */ = { isa = PBXGroup; children = ( @@ -955,15 +1017,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 +1042,7 @@ 671462351EB3396E00EAB194 /* Structures */ = { isa = PBXGroup; children = ( - 67EB7FE92061666900BDD9FB /* Cursors */, + 677452B620627F9E0024EEEF /* DataLoading */, 671462361EB3396E00EAB194 /* Api */, 6771DFDC1EE99F52002DCDAE /* DateFormatting */, 671462381EB3396E00EAB194 /* DrawingOperations */, @@ -1121,6 +1182,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 /* AnyPaginationWrappableView.swift */, + 67EB8000206177D600BDD9FB /* PaginationWrapperDelegate.swift */, + ); + path = PaginationDataLoading; + sourceTree = ""; + }; 679C77D51F98F7A60094BE10 /* UIAlertController */ = { isa = PBXGroup; children = ( @@ -1137,29 +1282,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 +1331,13 @@ path = Cursors; sourceTree = ""; }; + 67EB7FF5206175CD00BDD9FB /* Views */ = { + isa = PBXGroup; + children = ( + ); + path = Views; + sourceTree = ""; + }; 78CFEE201C5C456B00F50370 = { isa = PBXGroup; children = ( @@ -2082,8 +2240,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 */, @@ -2105,6 +2266,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 +2277,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 +2292,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 /* AnyPaginationWrappableView.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 +2312,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 +2341,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 +2384,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,12 +2392,14 @@ 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 */, @@ -2232,18 +2407,22 @@ 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 +2443,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 +2473,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 */, @@ -2301,7 +2487,7 @@ 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 +2501,13 @@ 67EB7FD220615B8900BDD9FB /* TotalCountCursorConfiguration.swift in Sources */, EFBE57D31EC35EF20040E00A /* Array+Extensions.swift in Sources */, 6714638B1EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */, + 67EB7FFF206176C900BDD9FB /* AnyPaginationWrappableView.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 +2516,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 +2536,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 +2558,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 +2594,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 /* AnyPaginationWrappableView.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,6 +2611,7 @@ 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 */, @@ -2421,6 +2622,7 @@ 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 +2633,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 +2655,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 +2669,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 +2681,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 100% rename from Sources/Classes/Cursors/FixedPageCursor.swift rename to Sources/Classes/DataLoading/Cursors/FixedPageCursor.swift diff --git a/Sources/Classes/Cursors/MapCursor.swift b/Sources/Classes/DataLoading/Cursors/MapCursor.swift similarity index 100% rename from Sources/Classes/Cursors/MapCursor.swift rename to Sources/Classes/DataLoading/Cursors/MapCursor.swift 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 100% rename from Sources/Classes/Cursors/StaticCursor.swift rename to Sources/Classes/DataLoading/Cursors/StaticCursor.swift diff --git a/Sources/Classes/Cursors/TotalCountCursor.swift b/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift similarity index 89% rename from Sources/Classes/Cursors/TotalCountCursor.swift rename to Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift index beadaf6d..0ba3d35d 100644 --- a/Sources/Classes/Cursors/TotalCountCursor.swift +++ b/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift @@ -23,10 +23,10 @@ import RxSwift import RxCocoa -public final class TotalCountCursor: ResettableCursorDataSource { +public final class TotalCountCursor: ResettableCursorType { - public typealias Element = CC.ListingType.ElementType - public typealias ResultType = [CC.ListingType.ElementType] + public typealias Element = CC.ResultType.ElementType + public typealias ResultType = [Element] private let configuration: CC @@ -34,8 +34,6 @@ public final class TotalCountCursor: Resettab public private(set) var totalCount: Int = .max - private let disposeBag = DisposeBag() - public var exhausted: Bool { return count >= totalCount } @@ -57,7 +55,7 @@ public final class TotalCountCursor: Resettab } 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..4943e87c --- /dev/null +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift @@ -0,0 +1,92 @@ +// +// 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 + + } + + override public func reload() { + load(.reload) + } + + override public func retry() { + load(.retry) + } + + public func loadMore() { + load(.next) + } + + private func load(_ loadType: LoadType) { + switch loadType { + case .reload, .retry: + dataSource = dataSource.reset() + + state = .loading(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 .loading(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/Pagination/PaginationViewModel.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationViewModel.swift similarity index 100% rename from Sources/Classes/Pagination/PaginationViewModel.swift rename to Sources/Classes/DataLoading/PaginationDataLoading/PaginationViewModel.swift diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift new file mode 100644 index 00000000..1af8c8a3 --- /dev/null +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift @@ -0,0 +1,339 @@ +// +// 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 PaginationViewModel 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: AnyPaginationWrappableView + 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: AnyPaginationWrappableView, 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.subscribe(onNext: { [weak self] offset in + self?.currentPlaceholderViewTopConstraint?.constant = -offset.y + }) + .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?.retryLoadMoreButtonIsAboutToHide() + 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 .loading = 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 .loading = 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?.retryLoadMoreButton(), + let retryButtonHeigth = delegate?.retryLoadMoreButtonHeight() else { + return + } + + retryButton.frame = CGRect(x: 0, y: 0, width: wrappedView.scrollView.bounds.width, height: retryButtonHeigth) + + retryButton.rx.controlEvent(.touchUpInside) + .bind { [weak self] in + self?.paginationViewModel.loadMore() + } + .disposed(by: disposeBag) + + delegate?.retryLoadMoreButtonIsAboutToShow() + + wrappedView.footerView = retryButton + } + } + + private func onEmptyState() { + defer { + wrappedView.scrollView.support.refreshControl?.endRefreshing() + } + 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 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 + + wrappedView.backgroundView = wrapperView + + 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) + .bind { [weak self] in + self?.reload() + } + .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(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 from, let after): + self?.onResultsState(newItems: newItems, from: from, afterState: after) + case .error(let error, let after): + self?.onErrorState(error: error, afterState: after) + case .empty: + self?.delegate?.clearView() + self?.onEmptyState() + case .exhausted: + self?.onExhaustedState() + } + }) + .disposed(by: disposeBag) + } + + private func removeCurrentPlaceholderView() { + wrappedView.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/DataLoading/RxDataLoadingModel.swift b/Sources/Classes/DataLoading/RxDataLoadingModel.swift new file mode 100644 index 00000000..a74461c1 --- /dev/null +++ b/Sources/Classes/DataLoading/RxDataLoadingModel.swift @@ -0,0 +1,113 @@ +// +// 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 + +class RxDataLoadingModel: DataLoadingModel + where DLS.DataSourceType: RxDataSource { + + public typealias LoadingStateType = DLS + + typealias DataSourceType = DLS.DataSourceType + typealias ResultType = DataSourceType.ResultType + + public typealias EmptyResultChecker = (ResultType) -> Bool + + private let stateVariable = Variable(.initialState) + var currentRequestDisposable: Disposable? + let scheduler = SerialDispatchQueueScheduler(qos: .default) + + var dataSource: DataSourceType + let emptyResultChecker: EmptyResultChecker + + public var stateDriver: Driver { + return stateVariable.asDriver() + } + + public init(dataSource: DataSourceType, emptyResultChecker: @escaping EmptyResultChecker) { + self.dataSource = dataSource + self.emptyResultChecker = emptyResultChecker + } + + public func reload() { + load(isRetry: false) + } + + public func retry() { + load(isRetry: true) + } + + private func load(isRetry: Bool) { + currentRequestDisposable?.dispose() + + if isRetry { + state = .initialState + } + + state = .loadingState(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() + .observeOn(scheduler) + .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/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..3c895a07 --- /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 loadingState(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..42effe47 --- /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 loading(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 loadingState(after: PaginationDataLoadingState) -> PaginationDataLoadingState { + return .loading(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..89b668ef 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 retryLoadMoreButton() -> 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 retryLoadMoreButtonHeight() -> CGFloat { return 44 } + func retryLoadMoreButtonIsAboutToShow() { + // by default - nothing will happen + } + + func retryLoadMoreButtonIsAboutToHide() { + // 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..f0e6899d --- /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 loadingState(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/AnyPaginationWrappableView.swift b/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappableView.swift new file mode 100644 index 00000000..e586f770 --- /dev/null +++ b/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappableView.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 struct AnyPaginationWrappableView: 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..ab5f6f26 --- /dev/null +++ b/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift @@ -0,0 +1,96 @@ +// +// 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 + + /// Delegate method that 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) + + /// Delegate method that 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) + + /// Delegate method that returns placeholder view for empty state. + /// + /// - Returns: Configured instace of UIView. + func emptyPlaceholder() -> UIView + + /// Delegate method that is 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 + + /// Delegate method that 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 + + /// Delegate method that 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 + + /// Delegate method that returns loading idicator for initial loading state. + /// + /// - Returns: Configured instace of AnyLoadingIndicator. + func loadingMoreIndicator() -> AnyLoadingIndicator + + /// Delegate method that returns instance of UIButton for "retry load more" action. + /// + /// - Returns: Configured instace of AnyLoadingIndicator. + func retryLoadMoreButton() -> UIButton + + /// Delegate method that returns preferred height for "retry load more" button. + /// + /// - Returns: Preferred height of "retry load more" button. + func retryLoadMoreButtonHeight() -> CGFloat + + /// Method is called before "retry load more" will be shown. + /// Typically, it's used when you need to show custom footer view. + func retryLoadMoreButtonIsAboutToShow() + + /// Method is called before "retry load more" will be hidden. + /// Typically, it's used when you need to hide custom footer view. + func retryLoadMoreButtonIsAboutToHide() + + /// Delegate method, used to clear view if placeholder is shown. + func clearView() +} From d20f795db7aacc342511dd89060ec67729fc4788 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 21 Mar 2018 16:51:25 +0300 Subject: [PATCH 02/10] code style --- .../PaginationWrapper.swift | 114 +++++++++++------- ...rapperDelegate+DefaultImplementation.swift | 4 +- .../PaginationWrapperDelegate.swift | 4 +- 3 files changed, 77 insertions(+), 45 deletions(-) diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift index 1af8c8a3..cf96f09e 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift @@ -29,9 +29,9 @@ final public class PaginationWrapper + fileprivate typealias DataLoadingModel = PaginationDataLoadingModel - private typealias LoadingState = DataLoadingModel.LoadingStateType + fileprivate typealias LoadingState = DataLoadingModel.LoadingStateType private var wrappedView: AnyPaginationWrappableView private let paginationViewModel: DataLoadingModel @@ -98,9 +98,9 @@ final public class PaginationWrapper) { - scrollObservable.subscribe(onNext: { [weak self] offset in - self?.currentPlaceholderViewTopConstraint?.constant = -offset.y - }) + scrollObservable + .asDriver(onErrorJustReturn: .zero) + .drive(scrollOffsetChanged) .disposed(by: disposeBag) } @@ -137,7 +137,7 @@ final public class PaginationWrapper { + return Binder(self) { base, value in + switch value { + case .initial: + base.onInitialState() + case .loading(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/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift b/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift index 89b668ef..27c12489 100644 --- a/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift +++ b/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift @@ -87,11 +87,11 @@ public extension PaginationWrapperDelegate { return 44 } - func retryLoadMoreButtonIsAboutToShow() { + func retryLoadMoreButtonWillBeShown() { // by default - nothing will happen } - func retryLoadMoreButtonIsAboutToHide() { + func retryLoadMoreButtonWillBeHidden() { // by default - nothing will happen } diff --git a/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift b/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift index ab5f6f26..d29bbff8 100644 --- a/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift +++ b/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift @@ -85,11 +85,11 @@ public protocol PaginationWrapperDelegate: class { /// Method is called before "retry load more" will be shown. /// Typically, it's used when you need to show custom footer view. - func retryLoadMoreButtonIsAboutToShow() + func retryLoadMoreButtonWillBeShown() /// Method is called before "retry load more" will be hidden. /// Typically, it's used when you need to hide custom footer view. - func retryLoadMoreButtonIsAboutToHide() + func retryLoadMoreButtonWillBeHidden() /// Delegate method, used to clear view if placeholder is shown. func clearView() From 0b472a1cdc41118ce9d3c2f03880d4adc244a866 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 21 Mar 2018 16:51:34 +0300 Subject: [PATCH 03/10] code review notes --- .../Classes/DataLoading/Cursors/TotalCountCursor.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift b/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift index 0ba3d35d..a13b0aeb 100644 --- a/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift +++ b/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift @@ -23,12 +23,12 @@ import RxSwift import RxCocoa -public final class TotalCountCursor: ResettableCursorType { +public final class TotalCountCursor: ResettableCursorType { - public typealias Element = CC.ResultType.ElementType + public typealias Element = CursorConfiguration.ResultType.ElementType public typealias ResultType = [Element] - private let configuration: CC + private let configuration: CursorConfiguration private var elements: [Element] = [] @@ -46,12 +46,12 @@ 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]> { From 435fbace1d2f649128090a032b76d3def5aecf11 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 21 Mar 2018 17:46:10 +0300 Subject: [PATCH 04/10] remove unused code --- LeadKit.xcodeproj/project.pbxproj | 10 -- .../PaginationViewModel.swift | 154 ------------------ .../PaginationWrapper.swift | 2 +- 3 files changed, 1 insertion(+), 165 deletions(-) delete mode 100644 Sources/Classes/DataLoading/PaginationDataLoading/PaginationViewModel.swift diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index b6379277..294ab30d 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -41,10 +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 */; }; - 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 */; }; @@ -512,7 +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 = ""; }; - 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 = ""; }; @@ -771,7 +766,6 @@ isa = PBXGroup; children = ( 6774529E20625EEE0024EEEF /* PaginationDataLoadingModel.swift */, - 671461D11EB3396E00EAB194 /* PaginationViewModel.swift */, 677452AD206274630024EEEF /* PaginationWrapper.swift */, ); path = PaginationDataLoading; @@ -2258,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 */, @@ -2403,7 +2396,6 @@ 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 */, @@ -2485,7 +2477,6 @@ 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 /* DataSource.swift in Sources */, 671462FF1EB3396E00EAB194 /* UIView+XibNameProtocol.swift in Sources */, @@ -2616,7 +2607,6 @@ 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 */, diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationViewModel.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationViewModel.swift deleted file mode 100644 index 888ccfec..00000000 --- a/Sources/Classes/DataLoading/PaginationDataLoading/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/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift index cf96f09e..8ad40b57 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift @@ -24,7 +24,7 @@ import RxSwift import RxCocoa import UIScrollView_InfiniteScroll -/// Class that connects PaginationViewModel with UIScrollView. It handles all non-visual and visual states. +/// 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] { From 323300be6384a0a3756f81efdbaac03843cbfc59 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 21 Mar 2018 18:28:21 +0300 Subject: [PATCH 05/10] fix .retry --- .../PaginationDataLoading/PaginationDataLoadingModel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift index 4943e87c..d4490762 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift @@ -50,6 +50,10 @@ public final class PaginationDataLoadingModel Date: Wed, 21 Mar 2018 18:55:53 +0300 Subject: [PATCH 06/10] add conformance to RxDataSource --- Sources/Classes/DataLoading/Cursors/FixedPageCursor.swift | 6 +++++- Sources/Classes/DataLoading/Cursors/MapCursor.swift | 6 +++++- Sources/Classes/DataLoading/Cursors/StaticCursor.swift | 4 +++- Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift | 2 +- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Sources/Classes/DataLoading/Cursors/FixedPageCursor.swift b/Sources/Classes/DataLoading/Cursors/FixedPageCursor.swift index df4c4ca8..44094546 100644 --- a/Sources/Classes/DataLoading/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/DataLoading/Cursors/MapCursor.swift b/Sources/Classes/DataLoading/Cursors/MapCursor.swift index 69647bc9..af29dff3 100644 --- a/Sources/Classes/DataLoading/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/DataLoading/Cursors/StaticCursor.swift b/Sources/Classes/DataLoading/Cursors/StaticCursor.swift index fca71d1b..a4bca0c5 100644 --- a/Sources/Classes/DataLoading/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/DataLoading/Cursors/TotalCountCursor.swift b/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift index a13b0aeb..6acf47b0 100644 --- a/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift +++ b/Sources/Classes/DataLoading/Cursors/TotalCountCursor.swift @@ -23,7 +23,7 @@ import RxSwift import RxCocoa -public final class TotalCountCursor: ResettableCursorType { +public final class TotalCountCursor: ResettableRxDataSourceCursor { public typealias Element = CursorConfiguration.ResultType.ElementType public typealias ResultType = [Element] From a503259eb150b11f47fa95aa491f5299a5d8ea41 Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Wed, 21 Mar 2018 19:22:51 +0300 Subject: [PATCH 07/10] fix runtime crash --- .../PaginationDataLoadingModel.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift index d4490762..3868c9ab 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift @@ -33,6 +33,15 @@ public final class PaginationDataLoadingModel Bool + + public override init(dataSource: Cursor, emptyResultChecker: @escaping PaginationEmptyResultChecker) { + super.init(dataSource: dataSource, emptyResultChecker: emptyResultChecker) + } + override public func reload() { load(.reload) } From 8ab4000b95f1e876d864de269c4db7b3c06fc93b Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Thu, 22 Mar 2018 13:15:23 +0300 Subject: [PATCH 08/10] make some types --- .../PaginationDataLoadingModel.swift | 4 ++-- Sources/Classes/DataLoading/RxDataLoadingModel.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift index 3868c9ab..053dc7c2 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift @@ -42,11 +42,11 @@ public final class PaginationDataLoadingModel: DataLoadingModel +open class RxDataLoadingModel: DataLoadingModel where DLS.DataSourceType: RxDataSource { public typealias LoadingStateType = DLS - typealias DataSourceType = DLS.DataSourceType - typealias ResultType = DataSourceType.ResultType + public typealias DataSourceType = DLS.DataSourceType + public typealias ResultType = DataSourceType.ResultType public typealias EmptyResultChecker = (ResultType) -> Bool @@ -40,7 +40,7 @@ class RxDataLoadingModel: DataLoadingModel var dataSource: DataSourceType let emptyResultChecker: EmptyResultChecker - public var stateDriver: Driver { + open var stateDriver: Driver { return stateVariable.asDriver() } @@ -49,11 +49,11 @@ class RxDataLoadingModel: DataLoadingModel self.emptyResultChecker = emptyResultChecker } - public func reload() { + open func reload() { load(isRetry: false) } - public func retry() { + open func retry() { load(isRetry: true) } From 7c012db9276d93912cc0b85de910ad41c9db62ab Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Thu, 22 Mar 2018 13:17:15 +0300 Subject: [PATCH 09/10] code review notes --- .../PaginationDataLoading/PaginationDataLoadingModel.swift | 4 ++-- .../PaginationDataLoading/PaginationWrapper.swift | 6 +++--- Sources/Classes/DataLoading/RxDataLoadingModel.swift | 4 +--- .../GeneralDataLoading/GeneralDataLoadingState.swift | 2 +- .../PaginationDataLoading/PaginationDataLoadingState.swift | 6 +++--- Sources/Protocols/DataLoading/DataLoadingState.swift | 2 +- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift index 053dc7c2..dc513b07 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationDataLoadingModel.swift @@ -63,7 +63,7 @@ public final class PaginationDataLoadingModel: DataLoadingModel private let stateVariable = Variable(.initialState) var currentRequestDisposable: Disposable? - let scheduler = SerialDispatchQueueScheduler(qos: .default) var dataSource: DataSourceType let emptyResultChecker: EmptyResultChecker @@ -64,7 +63,7 @@ open class RxDataLoadingModel: DataLoadingModel state = .initialState } - state = .loadingState(after: state) + state = .initialLoadingState(after: state) requestResult(from: dataSource) } @@ -89,7 +88,6 @@ open class RxDataLoadingModel: DataLoadingModel func requestResult(from dataSource: DataSourceType) { currentRequestDisposable = dataSource .resultSingle() - .observeOn(scheduler) .subscribe(onSuccess: { [weak self] result in self?.onGot(result: result, from: dataSource) }, onError: { [weak self] error in diff --git a/Sources/Enums/DataLoading/GeneralDataLoading/GeneralDataLoadingState.swift b/Sources/Enums/DataLoading/GeneralDataLoading/GeneralDataLoadingState.swift index 3c895a07..0b550995 100644 --- a/Sources/Enums/DataLoading/GeneralDataLoading/GeneralDataLoadingState.swift +++ b/Sources/Enums/DataLoading/GeneralDataLoading/GeneralDataLoadingState.swift @@ -38,7 +38,7 @@ public enum GeneralDataLoadingState: DataLoadingState { return .empty } - public static func loadingState(after: GeneralDataLoadingState) -> GeneralDataLoadingState { + public static func initialLoadingState(after: GeneralDataLoadingState) -> GeneralDataLoadingState { return .loading } diff --git a/Sources/Enums/DataLoading/PaginationDataLoading/PaginationDataLoadingState.swift b/Sources/Enums/DataLoading/PaginationDataLoading/PaginationDataLoadingState.swift index 42effe47..a995f774 100644 --- a/Sources/Enums/DataLoading/PaginationDataLoading/PaginationDataLoadingState.swift +++ b/Sources/Enums/DataLoading/PaginationDataLoading/PaginationDataLoadingState.swift @@ -23,7 +23,7 @@ public indirect enum PaginationDataLoadingState: DataLoadingState { case initial - case loading(after: PaginationDataLoadingState) + case initialLoading(after: PaginationDataLoadingState) case loadingMore(after: PaginationDataLoadingState) case results(newItems: DS.ResultType, from: DS, after: PaginationDataLoadingState) case error(error: Error, after: PaginationDataLoadingState) @@ -40,8 +40,8 @@ public indirect enum PaginationDataLoadingState: DataLoadingStat return .empty } - public static func loadingState(after: PaginationDataLoadingState) -> PaginationDataLoadingState { - return .loading(after: after) + public static func initialLoadingState(after: PaginationDataLoadingState) -> PaginationDataLoadingState { + return .initialLoading(after: after) } public static func resultState(result: DS.ResultType, diff --git a/Sources/Protocols/DataLoading/DataLoadingState.swift b/Sources/Protocols/DataLoading/DataLoadingState.swift index f0e6899d..29a02f72 100644 --- a/Sources/Protocols/DataLoading/DataLoadingState.swift +++ b/Sources/Protocols/DataLoading/DataLoadingState.swift @@ -35,7 +35,7 @@ public protocol DataLoadingState { /// /// - Parameter after: Previous state of data loading process. /// - Returns: Instance of loading state with given argument. - static func loadingState(after: Self) -> Self + static func initialLoadingState(after: Self) -> Self /// Method that returns result state from a specific data source after a given state. /// From b0cee2dee6abfab715bc08fdc1c1606a81f9a75a Mon Sep 17 00:00:00 2001 From: Ivan Smolin Date: Thu, 22 Mar 2018 14:55:43 +0300 Subject: [PATCH 10/10] code review notes --- LeadKit.xcodeproj/project.pbxproj | 16 +++++----- .../PaginationWrapper.swift | 32 +++++++++---------- .../DataLoading/RxDataLoadingModel.swift | 11 +++---- ...rapperDelegate+DefaultImplementation.swift | 8 ++--- ...iew.swift => AnyPaginationWrappable.swift} | 2 +- .../PaginationWrapperDelegate.swift | 31 +++++++++--------- 6 files changed, 49 insertions(+), 51 deletions(-) rename Sources/Structures/DataLoading/PaginationDataLoading/{AnyPaginationWrappableView.swift => AnyPaginationWrappable.swift} (97%) diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index 294ab30d..0d830e52 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -398,9 +398,9 @@ 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 /* AnyPaginationWrappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappableView.swift */; }; - 67EB7FFE206176C900BDD9FB /* AnyPaginationWrappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappableView.swift */; }; - 67EB7FFF206176C900BDD9FB /* AnyPaginationWrappableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappableView.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 */; }; @@ -623,7 +623,7 @@ 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 /* AnyPaginationWrappableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyPaginationWrappableView.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; }; @@ -1254,7 +1254,7 @@ 677452BA2062812C0024EEEF /* PaginationDataLoading */ = { isa = PBXGroup; children = ( - 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappableView.swift */, + 67EB7FFC206176C900BDD9FB /* AnyPaginationWrappable.swift */, 67EB8000206177D600BDD9FB /* PaginationWrapperDelegate.swift */, ); path = PaginationDataLoading; @@ -2289,7 +2289,7 @@ A676AE551F98112E001F9214 /* ObservableMappable.swift in Sources */, A6E0DDE11F8A696F002CA74E /* SeparatorRowBox.swift in Sources */, A6E0DDDE1F8A696F002CA74E /* EmptyCellRow.swift in Sources */, - 67EB7FFD206176C900BDD9FB /* AnyPaginationWrappableView.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 */, @@ -2492,7 +2492,7 @@ 67EB7FD220615B8900BDD9FB /* TotalCountCursorConfiguration.swift in Sources */, EFBE57D31EC35EF20040E00A /* Array+Extensions.swift in Sources */, 6714638B1EB3396E00EAB194 /* RoundDrawingOperation.swift in Sources */, - 67EB7FFF206176C900BDD9FB /* AnyPaginationWrappableView.swift in Sources */, + 67EB7FFF206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */, A676AE4A1F97D28A001F9214 /* String+Extensions.swift in Sources */, 671463831EB3396E00EAB194 /* PaddingDrawingOperation.swift in Sources */, 6774527C206252020024EEEF /* DataLoadingState.swift in Sources */, @@ -2589,7 +2589,7 @@ 671463491EB3396E00EAB194 /* ResettableType.swift in Sources */, A676AE491F97D28A001F9214 /* String+Extensions.swift in Sources */, 671462E51EB3396E00EAB194 /* UIColor+Hex.swift in Sources */, - 67EB7FFE206176C900BDD9FB /* AnyPaginationWrappableView.swift in Sources */, + 67EB7FFE206176C900BDD9FB /* AnyPaginationWrappable.swift in Sources */, 671462CD1EB3396E00EAB194 /* String+SizeCalculation.swift in Sources */, 677452B720627FE00024EEEF /* PaginationWrappable.swift in Sources */, 67EB7FC1206140E600BDD9FB /* TotalCountCursor.swift in Sources */, diff --git a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift index 9299b395..ab81267a 100644 --- a/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift +++ b/Sources/Classes/DataLoading/PaginationDataLoading/PaginationWrapper.swift @@ -29,11 +29,11 @@ final public class PaginationWrapper + private typealias DataLoadingModel = PaginationDataLoadingModel - fileprivate typealias LoadingState = DataLoadingModel.LoadingStateType + private typealias LoadingState = DataLoadingModel.LoadingStateType - private var wrappedView: AnyPaginationWrappableView + private var wrappedView: AnyPaginationWrappable private let paginationViewModel: DataLoadingModel private weak var delegate: Delegate? @@ -71,7 +71,7 @@ final public class PaginationWrapper { + private var stateChanged: Binder { return Binder(self) { base, value in switch value { case .initial: diff --git a/Sources/Classes/DataLoading/RxDataLoadingModel.swift b/Sources/Classes/DataLoading/RxDataLoadingModel.swift index 8babf8a8..29372a7a 100644 --- a/Sources/Classes/DataLoading/RxDataLoadingModel.swift +++ b/Sources/Classes/DataLoading/RxDataLoadingModel.swift @@ -23,12 +23,10 @@ import RxSwift import RxCocoa -open class RxDataLoadingModel: DataLoadingModel - where DLS.DataSourceType: RxDataSource { +open class RxDataLoadingModel: DataLoadingModel + where LoadingStateType.DataSourceType: RxDataSource { - public typealias LoadingStateType = DLS - - public typealias DataSourceType = DLS.DataSourceType + public typealias DataSourceType = LoadingStateType.DataSourceType public typealias ResultType = DataSourceType.ResultType public typealias EmptyResultChecker = (ResultType) -> Bool @@ -69,8 +67,7 @@ open class RxDataLoadingModel: DataLoadingModel } func onGot(error: Error) { - state = .errorState(error: error, - after: state) + state = .errorState(error: error, after: state) } private func onGot(result: ResultType, from dataSource: DataSourceType) { diff --git a/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift b/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift index 27c12489..6b5c5ac4 100644 --- a/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift +++ b/Sources/Extensions/DataLoading/PaginationDataLoading/PaginationWrapperDelegate+DefaultImplementation.swift @@ -75,7 +75,7 @@ public extension PaginationWrapperDelegate { return AnyLoadingIndicator(indicator) } - func retryLoadMoreButton() -> UIButton { + func footerRetryButton() -> UIButton { let retryButton = UIButton(type: .custom) retryButton.backgroundColor = .lightGray retryButton.setTitle("Retry load more", for: .normal) @@ -83,15 +83,15 @@ public extension PaginationWrapperDelegate { return retryButton } - func retryLoadMoreButtonHeight() -> CGFloat { + func footerRetryButtonHeight() -> CGFloat { return 44 } - func retryLoadMoreButtonWillBeShown() { + func footerRetryButtonWillAppear() { // by default - nothing will happen } - func retryLoadMoreButtonWillBeHidden() { + func footerRetryButtonWillDisappear() { // by default - nothing will happen } diff --git a/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappableView.swift b/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappable.swift similarity index 97% rename from Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappableView.swift rename to Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappable.swift index e586f770..a8ace596 100644 --- a/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappableView.swift +++ b/Sources/Structures/DataLoading/PaginationDataLoading/AnyPaginationWrappable.swift @@ -23,7 +23,7 @@ import UIKit /// Type that performs some kind of type erasure for PaginationWrappable. -public struct AnyPaginationWrappableView: PaginationWrappable { +public final class AnyPaginationWrappable: PaginationWrappable { private typealias ViewSetter = (UIView?) -> Void diff --git a/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift b/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift index d29bbff8..97f181d6 100644 --- a/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift +++ b/Sources/Structures/DataLoading/PaginationDataLoading/PaginationWrapperDelegate.swift @@ -26,7 +26,7 @@ public protocol PaginationWrapperDelegate: class { associatedtype DataSourceType: DataSource - /// Delegate method that handles loading new chunk of data. + /// Handles loading new chunk of data. /// /// - Parameters: /// - newItems: New items. @@ -34,7 +34,7 @@ public protocol PaginationWrapperDelegate: class { func paginationWrapper(didLoad newItems: DataSourceType.ResultType, using dataSource: DataSourceType) - /// Delegate method that handles reloading or initial loading of data. + /// Handles reloading or initial loading of data. /// /// - Parameters: /// - allItems: New items. @@ -42,12 +42,12 @@ public protocol PaginationWrapperDelegate: class { func paginationWrapper(didReload allItems: DataSourceType.ResultType, using dataSource: DataSourceType) - /// Delegate method that returns placeholder view for empty state. + /// Returns placeholder view for empty state. /// /// - Returns: Configured instace of UIView. func emptyPlaceholder() -> UIView - /// Delegate method that is called when initial loading error is occured. + /// Called when initial loading error is occured. /// It should return true if error is handled and false if it doesn't. /// /// - Parameters: @@ -55,42 +55,43 @@ public protocol PaginationWrapperDelegate: class { /// - Returns: Bool value. If true - then error placeholder wouldn't be shown. func customInitialLoadingErrorHandling(for error: Error) -> Bool - /// Delegate method that returns placeholder view for error state. + /// 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 - /// Delegate method that returns loading idicator for initial loading state. + /// 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 - /// Delegate method that returns loading idicator for initial loading state. + /// Returns loading idicator for initial loading state. /// /// - Returns: Configured instace of AnyLoadingIndicator. func loadingMoreIndicator() -> AnyLoadingIndicator - /// Delegate method that returns instance of UIButton for "retry load more" action. + /// Returns instance of UIButton for "retry load more" action. /// /// - Returns: Configured instace of AnyLoadingIndicator. - func retryLoadMoreButton() -> UIButton + func footerRetryButton() -> UIButton - /// Delegate method that returns preferred height for "retry load more" button. + /// Returns height for "retry load more" button. /// - /// - Returns: Preferred height of "retry load more" button. - func retryLoadMoreButtonHeight() -> CGFloat + /// - 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 retryLoadMoreButtonWillBeShown() + 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 retryLoadMoreButtonWillBeHidden() + func footerRetryButtonWillDisappear() - /// Delegate method, used to clear view if placeholder is shown. + /// Clears view when placeholder is shown. func clearView() + }