Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Example/RxRestClient.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
9A39C51D206A5DBA0036BA02 /* ImageUploadState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A39C51C206A5DBA0036BA02 /* ImageUploadState.swift */; };
9A39C520206A6F180036BA02 /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A39C51F206A6F180036BA02 /* UIImage+Extensions.swift */; };
9AA8F500216E0FED00F56506 /* RepositoryQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA8F4FF216E0FED00F56506 /* RepositoryQuery.swift */; };
B625ACEC221153D400BF3205 /* UIScrollView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B625ACEB221153D400BF3205 /* UIScrollView+Extensions.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -76,6 +77,7 @@
9AA8F4FF216E0FED00F56506 /* RepositoryQuery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryQuery.swift; sourceTree = "<group>"; };
A42DAEEC8A3AEE1412D49087 /* Pods-RxRestClient_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxRestClient_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxRestClient_Example/Pods-RxRestClient_Example.debug.xcconfig"; sourceTree = "<group>"; };
A69909A321AE479CD71890C3 /* Pods_RxRestClient_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxRestClient_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B625ACEB221153D400BF3205 /* UIScrollView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Extensions.swift"; sourceTree = "<group>"; };
C5407349FC1D9AC921C11477 /* RxRestClient.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = RxRestClient.podspec; path = ../RxRestClient.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
C8286D04B278F325D5FEBCD8 /* Pods_RxRestClient_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxRestClient_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F2D69DA486E24050EF561D90 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
Expand Down Expand Up @@ -243,6 +245,7 @@
isa = PBXGroup;
children = (
9A39C51F206A6F180036BA02 /* UIImage+Extensions.swift */,
B625ACEB221153D400BF3205 /* UIScrollView+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -463,6 +466,7 @@
9A28A2DE205BF6900051E02B /* RepositoriesState.swift in Sources */,
9A39C517206A55250036BA02 /* NewContact.swift in Sources */,
9A39C520206A6F180036BA02 /* UIImage+Extensions.swift in Sources */,
B625ACEC221153D400BF3205 /* UIScrollView+Extensions.swift in Sources */,
9A28A2E4205BFA370051E02B /* RepositoriesViewModel.swift in Sources */,
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
);
Expand Down Expand Up @@ -651,6 +655,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxRestClient_Example.app/RxRestClient_Example";
};
name = Debug;
};
Expand All @@ -664,6 +669,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.2;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RxRestClient_Example.app/RxRestClient_Example";
};
name = Release;
};
Expand Down
15 changes: 15 additions & 0 deletions Example/RxRestClient/Extensions/UIScrollView+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// UIScrollView+Extensions.swift
// RxRestClient_Example
//
// Created by Tigran Hambardzumyan on 2/11/19.
// Copyright © 2019 CocoaPods. All rights reserved.
//

import UIKit

extension UIScrollView {
func isNearBottomEdge(edgeOffset: CGFloat = 20.0) -> Bool {
return self.contentOffset.y + self.frame.size.height + edgeOffset > self.contentSize.height
}
}
24 changes: 2 additions & 22 deletions Example/RxRestClient/Models/RepositoriesState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,6 @@
import Foundation
import RxRestClient

struct RepositoriesState: ResponseState {

typealias Body = Data

var state: BaseState?
var data: [Repository]?

private init() {
state = nil
}

init(state: BaseState) {
self.state = state
}

init(response: (HTTPURLResponse, Data?)) {
if response.0.statusCode == 200, let body = response.1 {
self.data = try? JSONDecoder().decode(RepositoryResponse.self, from: body).items
}
}

static let empty = RepositoriesState()
final class RepositoriesState: PagingState<RepositoryResponse> {

}
28 changes: 25 additions & 3 deletions Example/RxRestClient/Models/Repository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import Foundation
import RxRestClient

struct Repository: Decodable {

Expand All @@ -17,12 +18,33 @@ struct Repository: Decodable {

}

struct RepositoryResponse: Decodable {
struct RepositoryResponse: PagingResponseProtocol {
let totalCount: Int
let items: [Repository]
var repositories: [Repository]

private enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case items
case repositories = "items"
}

// MARK: - PagingResponseProtocol
typealias Item = Repository

static var decoder: JSONDecoder {
return .init()
}

var canLoadMore: Bool {
return totalCount > items.count
}

var items: [Repository] {
get {
return repositories
}
set(newValue) {
repositories = newValue
}
}

}
17 changes: 16 additions & 1 deletion Example/RxRestClient/Models/RepositoryQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,22 @@
//

import Foundation
import RxSwift
import RxRestClient

struct RepositoryQuery: PagingQueryProtocol {

struct RepositoryQuery: Encodable {
let q: String
var page: Int

init(q: String) {
self.q = q
self.page = 1
}

func nextPage() -> RepositoryQuery {
var new = self
new.page += 1
return new
}
}
14 changes: 10 additions & 4 deletions Example/RxRestClient/Services/RepositoriesService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ import RxSwift
import RxRestClient

protocol RepositoriesServiceProtocol {
func get(query: RepositoryQuery) -> Observable<RepositoriesState>
func get(query: RepositoryQuery, loadNextPageTrigger: Observable<Void>) -> Observable<RepositoriesState>
}

final class RepositoriesService: RepositoriesServiceProtocol {

private let client = RxRestClient()
private let client: RxRestClient

func get(query: RepositoryQuery) -> Observable<RepositoriesState> {
return client.get("https://api.github.com/search/repositories", query: query)
init() {
var options = RxRestClientOptions.default
options.logger = DebugRxRestClientLogger()
self.client = RxRestClient(options: options)
}

func get(query: RepositoryQuery, loadNextPageTrigger: Observable<Void>) -> Observable<RepositoriesState> {
return client.get("https://api.github.com/search/repositories", query: query, loadNextPageTrigger: loadNextPageTrigger)
}
}
47 changes: 38 additions & 9 deletions Example/RxRestClient/ViewModels/RepositoriesViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,50 @@
import Foundation
import RxSwift
import RxCocoa
import RxRestClient

class RepositoriesViewModel {
final class RepositoriesViewModel {

let repositoriesState: Driver<RepositoriesState>
// MARK: - Inputs
let search = PublishRelay<String>()
let loadMore = PublishRelay<Void>()

init(search: ControlProperty<String>, service: RepositoriesServiceProtocol) {
// MARK: - Outputs
let repositories = BehaviorRelay<[Repository]>(value: [])
let baseState = PublishRelay<BaseState>()

repositoriesState = search
.asDriver()
.debounce(0.3)
// MARK: - Services
private let service: RepositoriesServiceProtocol

// MARK: - Private vars
private let disposeBag = DisposeBag()

// MARK: -
init(service: RepositoriesServiceProtocol) {

self.service = service

doBindings()
}

private func doBindings() {
let state = search
.throttle(0.3, scheduler: MainScheduler.instance)
.map { RepositoryQuery(q: $0) }
.flatMapLatest {
service.get(query: $0)
.asDriver(onErrorDriveWith: .never())
.flatMapLatest { [service, loadMore] query in
service.get(query: query, loadNextPageTrigger: loadMore.asObservable())
}
.share()

state.map { $0.state }
.filterNil()
.bind(to: baseState)
.disposed(by: disposeBag)

state.map { $0.response?.repositories ?? []}
.bind(to: repositories)
.disposed(by: disposeBag)

}

}
58 changes: 33 additions & 25 deletions Example/RxRestClient/Views/RepositoriesViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,55 +26,63 @@ class RepositoriesViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

viewModel = RepositoriesViewModel(search: searchBar.rx.text.orEmpty, service: RepositoriesService())
viewModel = RepositoriesViewModel(service: RepositoriesService())

doDriving()
doBindings()
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func doBindings() {
// Inputs
searchBar.rx.text.orEmpty.changed
.bind(to: viewModel.search)
.disposed(by: disposeBag)

tableView.rx.contentOffset
.flatMap { [unowned self] state in
return self.tableView.isNearBottomEdge(edgeOffset: 20.0)
? Signal.just(())
: Signal.empty()
}
.bind(to: viewModel.loadMore)
.disposed(by: disposeBag)

func doDriving() {
viewModel.repositoriesState
.map { $0.data ?? [] }
.drive(tableView.rx.items(cellIdentifier: "cell")) { _, element, cell in
// Outputs
viewModel.repositories
.bind(to: tableView.rx.items(cellIdentifier: "cell")) { _, element, cell in
cell.textLabel?.text = element.name
cell.detailTextLabel?.text = element.description
}
.disposed(by: disposeBag)

viewModel.repositoriesState
.map { $0.state?.validationProblem }
viewModel.baseState
.map { $0.validationProblem }
.filterNil()
.map { _ in "Please enter any search query" }
.drive(errorText)
.bind(to: errorText)
.disposed(by: disposeBag)

viewModel.repositoriesState
.map { $0.state?.forbidden }
viewModel.baseState
.map { $0.forbidden }
.filterNil()
.map { _ in "You have exceed API limit" }
.drive(errorText)
.bind(to: errorText)
.disposed(by: disposeBag)

viewModel.repositoriesState
.map { $0.data }
.filterNil()
.filter { $0.isEmpty }
viewModel.repositories
.withLatestFrom(viewModel.baseState) { $0.isEmpty && $1.isSuccess }
.filter { $0 }
.map { _ in "Unable to find repo with this search query" }
.drive(errorText)
.bind(to: errorText)
.disposed(by: disposeBag)

viewModel.repositoriesState
.map { $0.data?.isNotEmpty ?? false }
.filter { $0 }
viewModel.repositories
.filter { $0.isNotEmpty }
.map { _ in nil }
.drive(errorText)
.bind(to: errorText)
.disposed(by: disposeBag)

errorText
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [tableView] msg in
guard let msg = msg else {
tableView?.backgroundView = nil
Expand Down
Loading