├── SimpleDemoCleanArchitecture
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ ├── icon_20@2x.png
│ │ ├── icon_20@3x.png
│ │ ├── icon_29@2x.png
│ │ ├── icon_29@3x.png
│ │ ├── icon_40@2x.png
│ │ ├── icon_40@3x.png
│ │ ├── icon_60@2x.png
│ │ ├── icon_60@3x.png
│ │ ├── icon_1024@1x.png
│ │ ├── icon_20@2x-1.png
│ │ ├── icon_29@2x-1.png
│ │ ├── icon_40@2x-1.png
│ │ └── Contents.json
├── Domain
│ ├── Extensions
│ │ ├── UIViewController+Debug.swift
│ │ ├── Driver+.swift
│ │ └── ObservableType+.swift
│ ├── Architecture
│ │ ├── ViewModelType.swift
│ │ ├── BindableType.swift
│ │ ├── ErrorTracker.swift
│ │ ├── MultiActivityIndicator.swift
│ │ └── ActivityIndicator.swift
│ └── Models
│ │ ├── GithubOwner.swift
│ │ └── GithubRepo.swift
├── Extensions
│ ├── Array+.swift
│ ├── UIViewController+.swift
│ └── ViewController+Rx.swift
├── Scenes
│ ├── StoryBoards
│ │ ├── StoryBoards.swift
│ │ └── Base.lproj
│ │ │ └── Main.storyboard
│ ├── App
│ │ ├── AppUseCase.swift
│ │ ├── AppViewModel.swift
│ │ └── AppNavigator.swift
│ ├── RepoDetail
│ │ ├── RepoDetailUseCase.swift
│ │ ├── RepoDetailNavigator.swift
│ │ ├── RepoDetailViewModel.swift
│ │ └── RepoDetailViewController.swift
│ └── Main
│ │ ├── MainUseCase.swift
│ │ ├── Cell
│ │ ├── GithubRepoCell.swift
│ │ └── GithubRepoCell.xib
│ │ ├── MainNavigator.swift
│ │ ├── MainViewModel.swift
│ │ └── MainViewController.swift
├── Utils
│ └── URLs.swift
├── Platform
│ ├── Services
│ │ └── API
│ │ │ ├── Response
│ │ │ └── GithubRepoResponse.swift
│ │ │ ├── Request
│ │ │ └── GithubRepoRequest.swift
│ │ │ ├── APITarget.swift
│ │ │ └── APIService.swift
│ └── Repositories
│ │ └── GithubRepoRepository.swift
├── AppDelegate.swift
├── Info.plist
└── Base.lproj
│ └── LaunchScreen.storyboard
├── SimpleDemoCleanArchitecture.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── SimpleDemoCleanArchitecture.xcworkspace
├── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── contents.xcworkspacedata
├── Podfile
├── README.md
└── .gitignore
/SimpleDemoCleanArchitecture/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_20@2x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_20@3x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_29@2x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_29@3x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_40@2x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_40@3x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_60@2x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_60@3x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_1024@1x.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_20@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_20@2x-1.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_29@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_29@2x-1.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_40@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tgdong2296/SimpleDemoCleanArchitecture/HEAD/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/icon_40@2x-1.png
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Extensions/UIViewController+Debug.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import RxSwift
3 | import RxCocoa
4 |
5 | extension UIViewController {
6 | func logDeinit() {
7 | print(String(describing: type(of: self)) + " deinit")
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Extensions/Array+.swift:
--------------------------------------------------------------------------------
1 | extension Array {
2 | public var isNotEmpty: Bool {
3 | return !self.isEmpty
4 | }
5 |
6 | subscript (safe index: Index) -> Iterator.Element? {
7 | return indices.contains(index) ? self[index] : nil
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Architecture/ViewModelType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import RxSwift
3 | import RxCocoa
4 |
5 | protocol ViewModelType {
6 | associatedtype Input
7 | associatedtype Output
8 |
9 | func transform(_ input: Input, disposeBag: DisposeBag) -> Output
10 | }
11 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/StoryBoards/StoryBoards.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StoryBoards.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | struct StoryBoards {
13 | static let main = UIStoryboard(name: "Main", bundle: nil)
14 | }
15 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/App/AppUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppUseCase.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 |
13 | protocol AppUseCaseType {
14 |
15 | }
16 |
17 | struct AppUseCase: AppUseCaseType {
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/RepoDetail/RepoDetailUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepoDetailUseCase.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 |
13 | protocol RepoDetailUseCaseType {
14 |
15 | }
16 |
17 | struct RepoDetailUseCase: RepoDetailUseCaseType {
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Utils/URLs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLs.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct URLs {
12 | enum API {
13 | #if PRD
14 | static let baseUrl = "https://api.github.com"
15 | #else
16 | static let baseUrl = "https://api.github.com"
17 | #endif
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Platform/Services/API/Response/GithubRepoResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GithubRepoResponse.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct GithubRepoResponse: Codable {
12 | let githubRepos: [GithubRepo]
13 |
14 | enum CodingKeys: String, CodingKey {
15 | case githubRepos = "items"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/RepoDetail/RepoDetailNavigator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepoDetailNavigator.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import RxSwift
12 | import RxCocoa
13 |
14 | protocol RepoDetailNavigatorType {
15 |
16 | }
17 |
18 | struct RepoDetailNavigator: RepoDetailNavigatorType {
19 | unowned let navigationController: UINavigationController
20 | }
21 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Architecture/BindableType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import RxSwift
3 | import RxCocoa
4 |
5 | protocol BindableType: AnyObject {
6 | associatedtype ViewModelType
7 |
8 | var disposeBag: DisposeBag! { get set }
9 | var viewModel: ViewModelType! { get set }
10 |
11 | func bindViewModel()
12 | }
13 |
14 | extension BindableType where Self: UIViewController {
15 |
16 | func bindViewModel(to model: Self.ViewModelType) {
17 | viewModel = model
18 | loadViewIfNeeded()
19 | bindViewModel()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Models/GithubOwner.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GithubOwner.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by Giang Dong Trinh on 08/10/2023.
6 | // Copyright © 2023 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Then
11 |
12 | struct GithubOwner: Codable {
13 | let id: Int
14 | let avatarURL: String
15 | let url: String
16 |
17 | enum CodingKeys: String, CodingKey {
18 | case id = "id"
19 | case avatarURL = "avatar_url"
20 | case url = "url"
21 | }
22 | }
23 |
24 | extension GithubOwner: Then, Hashable {
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Platform/Services/API/Request/GithubRepoRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GithubRepoRequest.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 |
12 | struct GithubRepoRequest {
13 | let page: Int
14 | let perPage: Int
15 | let language: String
16 |
17 | init(page: Int, perPage: Int = 10, language: String = "language:swift") {
18 | self.page = page
19 | self.perPage = perPage
20 | self.language = language
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/Main/MainUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainUseCase.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 |
13 | protocol MainUseCaseType {
14 | func getRepos() -> Observable<[GithubRepo]>
15 | }
16 |
17 | struct MainUseCase: MainUseCaseType {
18 |
19 | func getRepos() -> Observable<[GithubRepo]> {
20 | let request = GithubRepoRequest(page: 1, perPage: 20)
21 | let repository = GithubRepoRepository()
22 | return repository.getGithubRepos(input: request)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/Main/Cell/GithubRepoCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GithubRepoCell.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SDWebImage
11 | import Reusable
12 |
13 | class GithubRepoCell: UITableViewCell, NibReusable {
14 | @IBOutlet weak var avatarImageView: UIImageView!
15 | @IBOutlet weak var nameLabel: UILabel!
16 |
17 | override func awakeFromNib() {
18 | super.awakeFromNib()
19 | }
20 |
21 | func setContentForCell(_ githubRepo: GithubRepo) {
22 | avatarImageView.sd_setImage(with: URL(string: githubRepo.owner.avatarURL), completed: nil)
23 | nameLabel.text = githubRepo.name
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Extensions/UIViewController+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIViewController {
13 |
14 | func showError(message: String, completion: (() -> Void)? = nil) {
15 | let ac = UIAlertController(title: "Error",
16 | message: message,
17 | preferredStyle: .alert)
18 | let okAction = UIAlertAction(title: "OK", style: .cancel) { _ in
19 | completion?()
20 | }
21 | ac.addAction(okAction)
22 | present(ac, animated: true, completion: nil)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Models/GithubRepo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GithubRepo.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Then
11 |
12 | struct GithubRepo: Codable {
13 | var id: Int
14 | var name: String
15 | var fullname: String
16 | var urlString: String
17 | var starCount: Int
18 | var folkCount: Int
19 | var owner: GithubOwner
20 |
21 | enum CodingKeys: String, CodingKey {
22 | case id = "id"
23 | case name = "name"
24 | case fullname = "full_name"
25 | case urlString = "html_url"
26 | case starCount = "stargazers_count"
27 | case folkCount = "forks"
28 | case owner = "owner"
29 | }
30 | }
31 |
32 | extension GithubRepo: Then, Hashable {
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Platform/Repositories/GithubRepoRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GithubRepoRepository.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 |
12 | protocol GithubRepoRepositoryType {
13 | func getGithubRepos(input request: GithubRepoRequest) -> Observable<[GithubRepo]>
14 | }
15 |
16 | class GithubRepoRepository: GithubRepoRepositoryType {
17 | private let apiService: APIService = APIService.shared
18 |
19 | func getGithubRepos(input request: GithubRepoRequest) -> Observable<[GithubRepo]> {
20 | return apiService.request(.repositories(request: request))
21 | .map(GithubRepoResponse.self)
22 | .asObservable()
23 | .map {
24 | $0.githubRepos
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/App/AppViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppViewModel.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 |
13 | struct AppViewModel {
14 | let navigator: AppNavigatorType
15 | let useCase: AppUseCaseType
16 | }
17 |
18 | extension AppViewModel: ViewModelType {
19 | struct Input {
20 | let loadTrigger: Driver
21 | }
22 |
23 | struct Output {
24 |
25 | }
26 |
27 | func transform(_ input: AppViewModel.Input, disposeBag: DisposeBag) -> AppViewModel.Output {
28 |
29 | input.loadTrigger
30 | .do(onNext: { _ in
31 | self.navigator.toMain()
32 | })
33 | .drive()
34 | .disposed(by: disposeBag)
35 |
36 | return Output()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/App/AppNavigator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppNavigator.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 |
13 | protocol AppNavigatorType {
14 | func toMain()
15 | }
16 |
17 | struct AppNavigator: AppNavigatorType {
18 | unowned let window: UIWindow
19 |
20 | func toMain() {
21 | let viewController = MainViewController.instantiate()
22 | let navigationController = UINavigationController(rootViewController: viewController)
23 | let navigator = MainNavigator(navigationController: navigationController)
24 | let useCase = MainUseCase()
25 | let viewModel = MainViewModel(navigator: navigator, useCase: useCase)
26 |
27 | viewController.bindViewModel(to: viewModel)
28 | window.rootViewController = navigationController
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Extensions/ViewController+Rx.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController+.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 | import MBProgressHUD
13 |
14 | extension Reactive where Base: UIViewController {
15 |
16 | var error: Binder {
17 | return Binder(base) { viewController, error in
18 | viewController.showError(message: error.localizedDescription)
19 | }
20 | }
21 |
22 | var isLoading: Binder {
23 | return Binder(base) { viewController, isLoading in
24 | if isLoading {
25 | let hud = MBProgressHUD.showAdded(to: viewController.view, animated: true)
26 | hud.offset.y = -30
27 | } else {
28 | MBProgressHUD.hide(for: viewController.view, animated: true)
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :ios, '14.0'
3 |
4 | target 'SimpleDemoCleanArchitecture' do
5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Clean Architecture
9 | pod 'Reusable', '~> 4.1.2'
10 | pod 'Then', '~> 2.7.0'
11 | pod 'SnapKit', '~> 5.0.1'
12 | pod 'Moya/RxSwift', '~> 15.0.0'
13 | pod 'RxAppState', '~> 1.7.1'
14 | pod 'RxDataSources', '~> 5.0.0'
15 | pod 'RxSwift', '~> 6.5.0'
16 | pod 'NSObject+Rx', '~> 5.2.2'
17 |
18 | # Others
19 | pod 'MBProgressHUD', '~> 1.2.0'
20 | pod 'SDWebImage', '~> 5.12.3'
21 | pod 'IQKeyboardManagerSwift', '~> 6.5.9'
22 | end
23 |
24 | post_install do |installer|
25 | installer.generated_projects.each do |project|
26 | project.targets.each do |target|
27 | target.build_configurations.each do |config|
28 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0'
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Architecture/ErrorTracker.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import RxSwift
3 | import RxCocoa
4 |
5 | class ErrorTracker: SharedSequenceConvertibleType {
6 | typealias SharingStrategy = DriverSharingStrategy
7 | private let _subject = PublishSubject()
8 |
9 | public init() {
10 |
11 | }
12 |
13 | func trackError(from source: O) -> Observable {
14 | return source.asObservable().do(onError: onError)
15 | }
16 |
17 | func asSharedSequence() -> SharedSequence {
18 | return _subject.asObservable().asDriverOnErrorJustComplete()
19 | }
20 |
21 | func asObservable() -> Observable {
22 | return _subject.asObservable()
23 | }
24 |
25 | private func onError(_ error: Error) {
26 | _subject.onNext(error)
27 | }
28 |
29 | deinit {
30 | _subject.onCompleted()
31 | }
32 | }
33 |
34 | extension ObservableConvertibleType {
35 | func trackError(_ errorTracker: ErrorTracker) -> Observable {
36 | return errorTracker.trackError(from: self)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/Main/MainNavigator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainNavigator.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import RxSwift
12 | import RxCocoa
13 |
14 | protocol MainNavigatorType {
15 | func toRepoDetail(githubRepo: GithubRepo)
16 | }
17 |
18 | struct MainNavigator: MainNavigatorType {
19 | unowned let navigationController: UINavigationController
20 |
21 | func toRepoDetail(githubRepo: GithubRepo) {
22 | let viewController = RepoDetailViewController.instantiate()
23 | let useCase = RepoDetailUseCase()
24 | let navigator = RepoDetailNavigator(navigationController: navigationController)
25 | let viewModel = RepoDetailViewModel(navigator: navigator,
26 | useCase: useCase,
27 | repo: githubRepo)
28 | viewController.bindViewModel(to: viewModel)
29 | navigationController.pushViewController(viewController, animated: true)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import RxSwift
11 | import RxCocoa
12 | import NSObject_Rx
13 |
14 | @UIApplicationMain
15 | class AppDelegate: UIResponder, UIApplicationDelegate {
16 |
17 | var disposeBag = DisposeBag()
18 | var window: UIWindow?
19 |
20 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
21 |
22 | bindViewModel()
23 |
24 | return true
25 | }
26 |
27 | private func bindViewModel() {
28 | guard let window = window else { return }
29 | let navigator = AppNavigator(window: window)
30 | let useCase = AppUseCase()
31 | let viewModel = AppViewModel(navigator: navigator, useCase: useCase)
32 |
33 | let input = AppViewModel.Input(loadTrigger: Driver.just(()))
34 | let _ = viewModel.transform(input, disposeBag: disposeBag)
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Extensions/Driver+.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import RxSwift
3 | import RxCocoa
4 |
5 | extension SharedSequenceConvertibleType {
6 |
7 | func mapToVoid() -> SharedSequence {
8 | return map { _ in }
9 | }
10 |
11 | func mapToOptional() -> SharedSequence {
12 | return map { value -> Element? in value }
13 | }
14 |
15 | func unwrap() -> SharedSequence where Element == T? {
16 | return flatMap { SharedSequence.from(optional: $0) }
17 | }
18 | }
19 |
20 | extension SharedSequenceConvertibleType where Element == Bool {
21 | func not() -> SharedSequence {
22 | return map(!)
23 | }
24 |
25 | static func or(_ sources: SharedSequence...)
26 | -> SharedSequence {
27 | return Driver.combineLatest(sources)
28 | .map { $0.reduce(false) { $0 || $1 } }
29 | }
30 |
31 | static func and(_ sources: SharedSequence...)
32 | -> SharedSequence {
33 | return Driver.combineLatest(sources)
34 | .map { $0.reduce(true) { $0 && $1 } }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/RepoDetail/RepoDetailViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepoDetailViewModel.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 |
13 | struct RepoDetailViewModel {
14 | let navigator: RepoDetailNavigatorType
15 | let useCase: RepoDetailUseCaseType
16 | let repo: GithubRepo
17 | }
18 |
19 | extension RepoDetailViewModel: ViewModelType {
20 | struct Input {
21 | let loadTrigger: Driver
22 | }
23 |
24 | struct Output {
25 | let repoName: Driver
26 | let repoImage: Driver
27 | let error: Driver
28 | let indicator: Driver
29 | }
30 |
31 | func transform(_ input: RepoDetailViewModel.Input, disposeBag: DisposeBag) -> RepoDetailViewModel.Output {
32 | let indicator = ActivityIndicator()
33 | let error = ErrorTracker()
34 |
35 | let repoName = input.loadTrigger
36 | .map { _ in
37 | return self.repo.name
38 | }
39 |
40 | let repoImageUrl = input.loadTrigger
41 | .map { _ in
42 | return self.repo.owner.avatarURL
43 | }
44 |
45 | return Output(
46 | repoName: repoName,
47 | repoImage: repoImageUrl,
48 | error: error.asDriver(),
49 | indicator: indicator.asDriver()
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Simple Demo Clean Architecture
2 | Demo project with Clean Architecture, MVVM and RxSwift
3 |
4 | ## RxSwift & RxCocoa Tutorials
5 |
6 | - [RxSwift: Các cách khởi tạo Observable trong RxSwift](https://viblo.asia/p/rxswift-cac-cach-khoi-tao-observable-trong-rxswift-aWj53pkPK6m).
7 | - [RxSwift: Các loại Subject trong RxSwift](https://viblo.asia/p/rxswift-cac-loai-subject-trong-rxswift-Eb85ogym52G).
8 | - [RxSwift: Phân biệt map, flatMap và flatMapLatest trong RxSwift](https://viblo.asia/p/rxswift-phan-biet-map-flatmap-va-flatmaplatest-trong-rxswift-ByEZkp4ylQ0).
9 | - [RxSwift: Combining Operator trong RxSwift](https://viblo.asia/p/rxswift-combining-operator-trong-rxswift-GrLZDvBn5k0).
10 | - [RxSwift: Trait trong RxSwift - Single, Completable, Maybe](https://viblo.asia/p/rxswift-trait-trong-rxswift-single-completable-maybe-L4x5xk2mlBM).
11 | - [RxSwift: Trait trong RxSwift - RxCocoa](https://viblo.asia/p/rxswift-trait-trong-rxswift-rxcocoa-aWj53pa1K6m).
12 |
13 | ## RxSwift Advance
14 | - [RxSwift: KVO - Key Value Observing](https://viblo.asia/p/rxswift-kvo-key-value-observing-ORNZqXa8K0n).
15 | - [RxSwift: Sử dụng Delegate Pattern trong RxSwift với DelegateProxy](https://viblo.asia/p/rxswift-su-dung-delegate-pattern-trong-rxswift-voi-delegateproxy-924lJje0lPM).
16 |
17 | ## MVVM & Clean Architecture
18 | - [RxSwift: Clean Architecture, MVVM và RxSwift (Phần 1)](https://viblo.asia/p/rxswift-clean-architecture-mvvm-va-rxswift-phan-1-gAm5yaR85db).
19 | - [RxSwift: Clean Architecture, MVVM và RxSwift (Phần 2)](https://viblo.asia/p/rxswift-clean-architecture-mvvm-va-rxswift-phan-2-E375zWR6KGW).
20 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Architecture/MultiActivityIndicator.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import RxSwift
3 | import RxCocoa
4 |
5 | class MultiActivityIndicator: ActivityIndicator {
6 | private let _lock = NSRecursiveLock()
7 | private let _set = BehaviorRelay>(value: [])
8 | private let _loading: SharedSequence
9 |
10 | override init() {
11 | _loading = _set
12 | .asDriver()
13 | .map { !$0.isEmpty }
14 | .distinctUntilChanged()
15 | }
16 |
17 | fileprivate func trackActivityOfObservable(_ source: O) -> Observable {
18 | let id = UUID().uuidString
19 |
20 | return source.asObservable()
21 | .do(onNext: { [weak self] _ in
22 | self?.sendStopLoading(id: id)
23 | }, onError: { [weak self] _ in
24 | self?.sendStopLoading(id: id)
25 | }, onCompleted: { [weak self] in
26 | self?.sendStopLoading(id: id)
27 | }, onSubscribe: { [weak self] in
28 | self?.subscribed(id: id)
29 | })
30 | }
31 |
32 | private func subscribed(id: String) {
33 | _lock.lock()
34 | var set = _set.value
35 | set.insert(id)
36 | _set.accept(set)
37 | _lock.unlock()
38 | }
39 |
40 | private func sendStopLoading(id: String) {
41 | _lock.lock()
42 | var set = _set.value
43 | set.remove(id)
44 | _set.accept(set)
45 | _lock.unlock()
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Architecture/ActivityIndicator.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import RxSwift
3 | import RxCocoa
4 |
5 | class ActivityIndicator: SharedSequenceConvertibleType {
6 | typealias Element = Bool
7 | typealias SharingStrategy = DriverSharingStrategy
8 |
9 | private let _lock = NSRecursiveLock()
10 | private let _variable = BehaviorRelay(value: false)
11 | private let _loading: SharedSequence
12 |
13 | init() {
14 | _loading = _variable.asDriver()
15 | .distinctUntilChanged()
16 | }
17 |
18 | fileprivate func trackActivityOfObservable(_ source: O) -> Observable {
19 | return source.asObservable()
20 | .do(onNext: { _ in
21 | self.sendStopLoading()
22 | }, onError: { _ in
23 | self.sendStopLoading()
24 | }, onCompleted: {
25 | self.sendStopLoading()
26 | }, onSubscribe: subscribed)
27 | }
28 |
29 | private func subscribed() {
30 | _lock.lock()
31 | _variable.accept(true)
32 | _lock.unlock()
33 | }
34 |
35 | private func sendStopLoading() {
36 | _lock.lock()
37 | _variable.accept(false)
38 | _lock.unlock()
39 | }
40 |
41 | func asSharedSequence() -> SharedSequence {
42 | return _loading
43 | }
44 | }
45 |
46 | extension ObservableConvertibleType {
47 | func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable {
48 | return activityIndicator.trackActivityOfObservable(self)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Simple Demo
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | # Package.resolved
41 | .build/
42 |
43 | # CocoaPods
44 | #
45 | # We recommend against adding the Pods directory to your .gitignore. However
46 | # you should judge for yourself, the pros and cons are mentioned at:
47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
48 | #
49 | # Pods/
50 |
51 | # Carthage
52 | #
53 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
54 | # Carthage/Checkouts
55 |
56 | Carthage/Build
57 |
58 | # fastlane
59 | #
60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61 | # screenshots whenever they are needed.
62 | # For more information about the recommended setup visit:
63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots/**/*.png
68 | fastlane/test_output
69 |
70 | .DS_Store
71 | Podfile.lock
72 | Pods/
73 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Domain/Extensions/ObservableType+.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import RxSwift
3 | import RxCocoa
4 |
5 | extension ObservableType {
6 |
7 | func catchErrorJustComplete() -> Observable {
8 | return `catch` { _ in
9 | return Observable.empty()
10 | }
11 | }
12 |
13 | func asDriverOnErrorJustComplete() -> Driver {
14 | return asDriver { _ in
15 | return Driver.empty()
16 | }
17 | }
18 |
19 | func mapToVoid() -> Observable {
20 | return map { _ in }
21 | }
22 |
23 | func mapToOptional() -> Observable {
24 | return map { value -> Element? in value }
25 | }
26 |
27 | func unwrap() -> Observable where Element == T? {
28 | return flatMap { Observable.from(optional: $0) }
29 | }
30 | }
31 |
32 | extension ObservableType where Element == Bool {
33 | func not() -> Observable {
34 | return map(!)
35 | }
36 |
37 | static func or(_ sources: Observable...)
38 | -> Observable {
39 | return Observable.combineLatest(sources)
40 | .map { $0.reduce(false) { $0 || $1 } }
41 | }
42 |
43 | static func and(_ sources: Observable...)
44 | -> Observable {
45 | return Observable.combineLatest(sources)
46 | .map { $0.reduce(true) { $0 && $1 } }
47 | }
48 | }
49 |
50 | private func getThreadName() -> String {
51 | if Thread.current.isMainThread {
52 | return "Main Thread"
53 | } else if let name = Thread.current.name {
54 | if name.isEmpty {
55 | return "Anonymous Thread"
56 | }
57 | return name
58 | } else {
59 | return "Unknown Thread"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/Main/MainViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewModel.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 |
13 | struct MainViewModel {
14 | let navigator: MainNavigatorType
15 | let useCase: MainUseCaseType
16 | }
17 |
18 | extension MainViewModel: ViewModelType {
19 | struct Input {
20 | let loadTrigger: Driver
21 | let selectTrigger: Driver
22 | }
23 |
24 | struct Output {
25 | let repos: Driver<[GithubRepo]>
26 | let error: Driver
27 | let indicator: Driver
28 | }
29 |
30 | func transform(_ input: MainViewModel.Input, disposeBag: DisposeBag) -> MainViewModel.Output {
31 | let indicator = ActivityIndicator()
32 | let error = ErrorTracker()
33 |
34 | let repos = input.loadTrigger
35 | .flatMapLatest { _ in
36 | return self.useCase.getRepos()
37 | .trackActivity(indicator)
38 | .trackError(error)
39 | .asDriverOnErrorJustComplete()
40 | }
41 |
42 | input.selectTrigger
43 | .withLatestFrom(repos) { indexPath, repos in
44 | return repos[indexPath.row]
45 | }
46 | .do(onNext: { repo in
47 | self.navigator.toRepoDetail(githubRepo: repo)
48 | })
49 | .drive()
50 | .disposed(by: disposeBag)
51 |
52 | return Output(
53 | repos: repos,
54 | error: error.asDriver(),
55 | indicator: indicator.asDriver()
56 | )
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/Main/MainViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Foundation
11 | import RxSwift
12 | import RxCocoa
13 | import Then
14 | import NSObject_Rx
15 | import Reusable
16 |
17 | class MainViewController: UIViewController, BindableType {
18 | @IBOutlet weak var tableView: UITableView!
19 |
20 | var disposeBag: DisposeBag! = DisposeBag()
21 | var viewModel: MainViewModel!
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | configView()
27 | }
28 |
29 | private func configView() {
30 | title = "Github"
31 | tableView.do {
32 | $0.register(cellType: GithubRepoCell.self)
33 | $0.rowHeight = 80
34 | }
35 | }
36 |
37 | func bindViewModel() {
38 | let input = MainViewModel.Input(
39 | loadTrigger: Driver.just(()),
40 | selectTrigger: tableView.rx.itemSelected.asDriver()
41 | )
42 | let output = viewModel.transform(input, disposeBag: disposeBag)
43 |
44 | output.repos
45 | .drive(tableView.rx.items) { tableView, index, repo in
46 | let indexPath = IndexPath(item: index, section: 0)
47 | let cell: GithubRepoCell = tableView.dequeueReusableCell(for: indexPath)
48 | cell.setContentForCell(repo)
49 | return cell
50 | }
51 | .disposed(by: rx.disposeBag)
52 |
53 | output.indicator
54 | .drive(rx.isLoading)
55 | .disposed(by: rx.disposeBag)
56 |
57 | output.error
58 | .drive(rx.error)
59 | .disposed(by: rx.disposeBag)
60 | }
61 | }
62 |
63 | extension MainViewController: StoryboardSceneBased {
64 | static var sceneStoryboard = StoryBoards.main
65 | }
66 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/RepoDetail/RepoDetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepoDetailViewController.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by trinh.giang.dong on 2/22/19.
6 | // Copyright © 2019 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Foundation
11 | import RxSwift
12 | import RxCocoa
13 | import Then
14 | import NSObject_Rx
15 | import Reusable
16 | import SDWebImage
17 |
18 | class RepoDetailViewController: UIViewController, BindableType {
19 | @IBOutlet weak var avatarImageView: UIImageView!
20 | @IBOutlet weak var nameLabel: UILabel!
21 |
22 | var disposeBag: DisposeBag! = DisposeBag()
23 | var viewModel: RepoDetailViewModel!
24 |
25 | override func viewDidLoad() {
26 | super.viewDidLoad()
27 |
28 | configView()
29 | }
30 |
31 | private func configView() {
32 | title = "Gitgub Detail"
33 | }
34 |
35 | func bindViewModel() {
36 | let input = RepoDetailViewModel.Input(
37 | loadTrigger: Driver.just(())
38 | )
39 | let output = viewModel.transform(input, disposeBag: disposeBag)
40 |
41 | output.repoImage
42 | .drive(avatarBinding)
43 | .disposed(by: rx.disposeBag)
44 |
45 | output.repoName
46 | .drive(nameLabel.rx.text)
47 | .disposed(by: rx.disposeBag)
48 |
49 | output.indicator
50 | .drive(rx.isLoading)
51 | .disposed(by: rx.disposeBag)
52 |
53 | output.error
54 | .drive(rx.error)
55 | .disposed(by: rx.disposeBag)
56 | }
57 | }
58 |
59 | extension RepoDetailViewController {
60 |
61 | var avatarBinding: Binder {
62 | return Binder(self) { viewController, imageUrl in
63 | let url = URL(string: imageUrl)
64 | viewController.avatarImageView.sd_setImage(with: url, completed: nil)
65 | }
66 | }
67 | }
68 |
69 | extension RepoDetailViewController: StoryboardSceneBased {
70 | static var sceneStoryboard = StoryBoards.main
71 | }
72 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Platform/Services/API/APITarget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APITarget.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by Giang Dong Trinh on 08/10/2023.
6 | // Copyright © 2023 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Moya
11 |
12 | enum APITarget {
13 | case repositories(request: GithubRepoRequest)
14 | }
15 |
16 | extension APITarget: TargetType {
17 |
18 | var baseURL: URL {
19 | let domain = URLs.API.baseUrl
20 | guard let url = URL(string: domain) else {
21 | fatalError("Invalid base URL.")
22 | }
23 | return url
24 | }
25 |
26 | var headers: [String : String]? {
27 | switch self {
28 | case .repositories:
29 | return [
30 | "Content-type": "application/json",
31 | "Accept": "application/json"
32 | ]
33 | }
34 | }
35 |
36 | var path: String {
37 | switch self {
38 | case .repositories:
39 | return "/search/repositories"
40 | }
41 | }
42 |
43 | var method: Moya.Method {
44 | switch self {
45 | case .repositories:
46 | return .get
47 | }
48 | }
49 |
50 | var task: Task {
51 | switch self {
52 | case .repositories(let request):
53 | let parameters: [String: Any] = [
54 | "q": request.language,
55 | "per_page": request.perPage,
56 | "page": request.page
57 | ]
58 | return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
59 | }
60 | }
61 |
62 | var sampleData: Data {
63 | // Bundle.main.loadData(fileName: "") ?? Data()
64 | return Data()
65 | }
66 | }
67 |
68 | extension String {
69 | /// Throw crash error if API did not define in documentation.
70 | static var throwDocumentError: String {
71 | fatalError("API document did not define!")
72 | }
73 | }
74 |
75 | extension Moya.Method {
76 | /// Throw crash error if API did not define in documentation.
77 | static var throwDocumentError: Moya.Method {
78 | fatalError("API document did not define!")
79 | }
80 | }
81 |
82 | extension Task {
83 | /// Throw crash error if API did not define in documentation.
84 | static var throwDocumentError: Task {
85 | fatalError("API document did not define!")
86 | }
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "icon_20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "icon_20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "icon_29@2x.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "icon_29@3x.png",
25 | "scale" : "3x"
26 | },
27 | {
28 | "size" : "40x40",
29 | "idiom" : "iphone",
30 | "filename" : "icon_40@2x-1.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "icon_40@3x.png",
37 | "scale" : "3x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "icon_60@2x.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "icon_60@3x.png",
49 | "scale" : "3x"
50 | },
51 | {
52 | "idiom" : "ipad",
53 | "size" : "20x20",
54 | "scale" : "1x"
55 | },
56 | {
57 | "size" : "20x20",
58 | "idiom" : "ipad",
59 | "filename" : "icon_20@2x-1.png",
60 | "scale" : "2x"
61 | },
62 | {
63 | "idiom" : "ipad",
64 | "size" : "29x29",
65 | "scale" : "1x"
66 | },
67 | {
68 | "size" : "29x29",
69 | "idiom" : "ipad",
70 | "filename" : "icon_29@2x-1.png",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "40x40",
76 | "scale" : "1x"
77 | },
78 | {
79 | "size" : "40x40",
80 | "idiom" : "ipad",
81 | "filename" : "icon_40@2x.png",
82 | "scale" : "2x"
83 | },
84 | {
85 | "idiom" : "ipad",
86 | "size" : "76x76",
87 | "scale" : "1x"
88 | },
89 | {
90 | "idiom" : "ipad",
91 | "size" : "76x76",
92 | "scale" : "2x"
93 | },
94 | {
95 | "idiom" : "ipad",
96 | "size" : "83.5x83.5",
97 | "scale" : "2x"
98 | },
99 | {
100 | "size" : "1024x1024",
101 | "idiom" : "ios-marketing",
102 | "filename" : "icon_1024@1x.png",
103 | "scale" : "1x"
104 | }
105 | ],
106 | "info" : {
107 | "version" : 1,
108 | "author" : "xcode"
109 | }
110 | }
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Platform/Services/API/APIService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIService.swift
3 | // SimpleDemoCleanArchitecture
4 | //
5 | // Created by Giang Dong Trinh on 08/10/2023.
6 | // Copyright © 2023 trinh.giang.dong. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import RxSwift
11 | import RxCocoa
12 | import Moya
13 | import Alamofire
14 |
15 | protocol APIServiceType {
16 | func request(_ target: APITarget) -> Single
17 | }
18 |
19 | class APIService: APIServiceType {
20 |
21 | static let shared = APIService()
22 |
23 | private let provider: MoyaProvider
24 | private let semaphore = DispatchSemaphore(value: 1)
25 |
26 | private init() {
27 | let configuration = URLSessionConfiguration.default.with {
28 | $0.headers = .default
29 | $0.timeoutIntervalForRequest = 30
30 | $0.timeoutIntervalForResource = 60
31 | }
32 |
33 | let session = Alamofire.Session(configuration: configuration)
34 |
35 | let networkPlugin = NetworkLoggerPlugin(configuration: NetworkLoggerPlugin.Configuration(logOptions: .verbose))
36 |
37 | provider = MoyaProvider(session: session, plugins: [networkPlugin])
38 | }
39 |
40 | // MARK: - Functions
41 | func request(_ target: APITarget) -> Single {
42 | let request = Single.just(())
43 | .do(onSuccess: { response in
44 | print("[LOG 🌏][\(target.method.rawValue)] Request \(target.baseURL.absoluteString + target.path)")
45 | })
46 | .flatMap { [unowned self] _ -> Single in
47 | return self.provider.rx.request(target)
48 | }
49 | .catchApiError()
50 | return request
51 | }
52 | }
53 |
54 | extension PrimitiveSequence where Trait == SingleTrait, Element == Response {
55 |
56 | func catchApiError() -> Single {
57 | let response = flatMap { response -> Single in
58 | switch response.statusCode {
59 | case 200...299:
60 | print("[LOG ✅] Request Success \(response.request?.url?.absoluteString ?? "")")
61 | return .just(response)
62 | default:
63 | print("[LOG ❌] Request Error \(response.statusCode) \(response.request?.url?.absoluteString ?? "")")
64 | let domain = response.request?.url?.absoluteString ?? ""
65 | let code = response.statusCode
66 | let errorMessage = HTTPURLResponse.localizedString(forStatusCode: response.statusCode)
67 | throw NSError(domain: domain,
68 | code: code,
69 | userInfo: [NSLocalizedDescriptionKey: errorMessage])
70 | }
71 | }
72 | return response
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/Main/Cell/GithubRepoCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture/Scenes/StoryBoards/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/SimpleDemoCleanArchitecture.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 714C2ECD221F83AC001F98FD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2ECC221F83AC001F98FD /* AppDelegate.swift */; };
11 | 714C2ED2221F83AC001F98FD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 714C2ED0221F83AC001F98FD /* Main.storyboard */; };
12 | 714C2ED4221F83AE001F98FD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 714C2ED3221F83AE001F98FD /* Assets.xcassets */; };
13 | 714C2ED7221F83AE001F98FD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 714C2ED5221F83AE001F98FD /* LaunchScreen.storyboard */; };
14 | 714C2EE1221F86B6001F98FD /* AppViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EE0221F86B6001F98FD /* AppViewModel.swift */; };
15 | 714C2EE3221F86C1001F98FD /* AppNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EE2221F86C1001F98FD /* AppNavigator.swift */; };
16 | 714C2EE5221F86D0001F98FD /* AppUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EE4221F86D0001F98FD /* AppUseCase.swift */; };
17 | 714C2EE8221F8951001F98FD /* StoryBoards.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EE7221F8951001F98FD /* StoryBoards.swift */; };
18 | 714C2EEE221F89C7001F98FD /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EED221F89C7001F98FD /* MainViewController.swift */; };
19 | 714C2EF0221F8EC1001F98FD /* MainNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EEF221F8EC1001F98FD /* MainNavigator.swift */; };
20 | 714C2EF2221F8ECA001F98FD /* MainUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EF1221F8ECA001F98FD /* MainUseCase.swift */; };
21 | 714C2EF4221F8ED6001F98FD /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EF3221F8ED6001F98FD /* MainViewModel.swift */; };
22 | 714C2EF9221F9238001F98FD /* GithubRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2EF8221F9238001F98FD /* GithubRepo.swift */; };
23 | 714C2F0F221F97F8001F98FD /* URLs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F0E221F97F8001F98FD /* URLs.swift */; };
24 | 714C2F13221F988A001F98FD /* GithubRepoResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F12221F988A001F98FD /* GithubRepoResponse.swift */; };
25 | 714C2F1B221FC7D2001F98FD /* GithubRepoRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F1A221FC7D2001F98FD /* GithubRepoRepository.swift */; };
26 | 714C2F21221FCAC6001F98FD /* GithubRepoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F1F221FCAC6001F98FD /* GithubRepoCell.swift */; };
27 | 714C2F22221FCAC6001F98FD /* GithubRepoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 714C2F20221FCAC6001F98FD /* GithubRepoCell.xib */; };
28 | 714C2F25221FCD5D001F98FD /* ViewController+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F24221FCD5D001F98FD /* ViewController+Rx.swift */; };
29 | 714C2F27221FCDE2001F98FD /* UIViewController+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F26221FCDE2001F98FD /* UIViewController+.swift */; };
30 | 714C2F2A221FD75F001F98FD /* RepoDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F29221FD75F001F98FD /* RepoDetailViewModel.swift */; };
31 | 714C2F2C221FD76E001F98FD /* RepoDetailUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F2B221FD76E001F98FD /* RepoDetailUseCase.swift */; };
32 | 714C2F2E221FD77D001F98FD /* RepoDetailNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F2D221FD77D001F98FD /* RepoDetailNavigator.swift */; };
33 | 714C2F30221FD791001F98FD /* RepoDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 714C2F2F221FD791001F98FD /* RepoDetailViewController.swift */; };
34 | 9E0BA0B62AD285BA00752733 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 9E0BA0B52AD285BA00752733 /* README.md */; };
35 | 9E0BA0B92AD2876300752733 /* APITarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0B82AD2876300752733 /* APITarget.swift */; };
36 | 9E0BA0BB2AD287B200752733 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0BA2AD287B200752733 /* APIService.swift */; };
37 | 9E0BA0BE2AD2882500752733 /* GithubRepoRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0BD2AD2882500752733 /* GithubRepoRequest.swift */; };
38 | 9E0BA0D42AD28C2B00752733 /* BindableType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0C12AD28C2B00752733 /* BindableType.swift */; };
39 | 9E0BA0D62AD28C2B00752733 /* ErrorTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0C32AD28C2B00752733 /* ErrorTracker.swift */; };
40 | 9E0BA0D72AD28C2B00752733 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0C42AD28C2B00752733 /* ActivityIndicator.swift */; };
41 | 9E0BA0DD2AD28C2B00752733 /* MultiActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0CA2AD28C2B00752733 /* MultiActivityIndicator.swift */; };
42 | 9E0BA0DE2AD28C2B00752733 /* Driver+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0CC2AD28C2B00752733 /* Driver+.swift */; };
43 | 9E0BA0DF2AD28C2B00752733 /* ObservableType+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0CD2AD28C2B00752733 /* ObservableType+.swift */; };
44 | 9E0BA0E02AD28C2B00752733 /* Array+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0CE2AD28C2B00752733 /* Array+.swift */; };
45 | 9E0BA0E22AD28C2B00752733 /* UIViewController+Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0D02AD28C2B00752733 /* UIViewController+Debug.swift */; };
46 | 9E0BA0E62AD28C8F00752733 /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0E52AD28C8F00752733 /* ViewModelType.swift */; };
47 | 9E0BA0E82AD28FF600752733 /* GithubOwner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0BA0E72AD28FF600752733 /* GithubOwner.swift */; };
48 | AC288A3A913FC0EA073E7B7D /* Pods_SimpleDemoCleanArchitecture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A3A01E9CB332F807E2695B53 /* Pods_SimpleDemoCleanArchitecture.framework */; };
49 | /* End PBXBuildFile section */
50 |
51 | /* Begin PBXFileReference section */
52 | 37869304476713634AA3A366 /* Pods-SimpleDemoCleanArchitecture.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimpleDemoCleanArchitecture.release.xcconfig"; path = "Pods/Target Support Files/Pods-SimpleDemoCleanArchitecture/Pods-SimpleDemoCleanArchitecture.release.xcconfig"; sourceTree = ""; };
53 | 5BD36DC1B11198D43E3DE2E5 /* Pods-SimpleDemoCleanArchitecture.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SimpleDemoCleanArchitecture.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SimpleDemoCleanArchitecture/Pods-SimpleDemoCleanArchitecture.debug.xcconfig"; sourceTree = ""; };
54 | 714C2EC9221F83AC001F98FD /* SimpleDemoCleanArchitecture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleDemoCleanArchitecture.app; sourceTree = BUILT_PRODUCTS_DIR; };
55 | 714C2ECC221F83AC001F98FD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
56 | 714C2ED1221F83AC001F98FD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
57 | 714C2ED3221F83AE001F98FD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
58 | 714C2ED6221F83AE001F98FD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
59 | 714C2ED8221F83AE001F98FD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
60 | 714C2EE0221F86B6001F98FD /* AppViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppViewModel.swift; sourceTree = ""; };
61 | 714C2EE2221F86C1001F98FD /* AppNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigator.swift; sourceTree = ""; };
62 | 714C2EE4221F86D0001F98FD /* AppUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUseCase.swift; sourceTree = ""; };
63 | 714C2EE7221F8951001F98FD /* StoryBoards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryBoards.swift; sourceTree = ""; };
64 | 714C2EED221F89C7001F98FD /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; };
65 | 714C2EEF221F8EC1001F98FD /* MainNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainNavigator.swift; sourceTree = ""; };
66 | 714C2EF1221F8ECA001F98FD /* MainUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainUseCase.swift; sourceTree = ""; };
67 | 714C2EF3221F8ED6001F98FD /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; };
68 | 714C2EF8221F9238001F98FD /* GithubRepo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubRepo.swift; sourceTree = ""; };
69 | 714C2F0E221F97F8001F98FD /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; };
70 | 714C2F12221F988A001F98FD /* GithubRepoResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubRepoResponse.swift; sourceTree = ""; };
71 | 714C2F1A221FC7D2001F98FD /* GithubRepoRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubRepoRepository.swift; sourceTree = ""; };
72 | 714C2F1F221FCAC6001F98FD /* GithubRepoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubRepoCell.swift; sourceTree = ""; };
73 | 714C2F20221FCAC6001F98FD /* GithubRepoCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GithubRepoCell.xib; sourceTree = ""; };
74 | 714C2F24221FCD5D001F98FD /* ViewController+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController+Rx.swift"; sourceTree = ""; };
75 | 714C2F26221FCDE2001F98FD /* UIViewController+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+.swift"; sourceTree = ""; };
76 | 714C2F29221FD75F001F98FD /* RepoDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepoDetailViewModel.swift; sourceTree = ""; };
77 | 714C2F2B221FD76E001F98FD /* RepoDetailUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepoDetailUseCase.swift; sourceTree = ""; };
78 | 714C2F2D221FD77D001F98FD /* RepoDetailNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepoDetailNavigator.swift; sourceTree = ""; };
79 | 714C2F2F221FD791001F98FD /* RepoDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepoDetailViewController.swift; sourceTree = ""; };
80 | 9E0BA0B52AD285BA00752733 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
81 | 9E0BA0B82AD2876300752733 /* APITarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APITarget.swift; sourceTree = ""; };
82 | 9E0BA0BA2AD287B200752733 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; };
83 | 9E0BA0BD2AD2882500752733 /* GithubRepoRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GithubRepoRequest.swift; sourceTree = ""; };
84 | 9E0BA0C12AD28C2B00752733 /* BindableType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BindableType.swift; sourceTree = ""; };
85 | 9E0BA0C32AD28C2B00752733 /* ErrorTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorTracker.swift; sourceTree = ""; };
86 | 9E0BA0C42AD28C2B00752733 /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; };
87 | 9E0BA0CA2AD28C2B00752733 /* MultiActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultiActivityIndicator.swift; sourceTree = ""; };
88 | 9E0BA0CC2AD28C2B00752733 /* Driver+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Driver+.swift"; sourceTree = ""; };
89 | 9E0BA0CD2AD28C2B00752733 /* ObservableType+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ObservableType+.swift"; sourceTree = ""; };
90 | 9E0BA0CE2AD28C2B00752733 /* Array+.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+.swift"; sourceTree = ""; };
91 | 9E0BA0D02AD28C2B00752733 /* UIViewController+Debug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Debug.swift"; sourceTree = ""; };
92 | 9E0BA0E52AD28C8F00752733 /* ViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; };
93 | 9E0BA0E72AD28FF600752733 /* GithubOwner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubOwner.swift; sourceTree = ""; };
94 | A3A01E9CB332F807E2695B53 /* Pods_SimpleDemoCleanArchitecture.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SimpleDemoCleanArchitecture.framework; sourceTree = BUILT_PRODUCTS_DIR; };
95 | /* End PBXFileReference section */
96 |
97 | /* Begin PBXFrameworksBuildPhase section */
98 | 714C2EC6221F83AC001F98FD /* Frameworks */ = {
99 | isa = PBXFrameworksBuildPhase;
100 | buildActionMask = 2147483647;
101 | files = (
102 | AC288A3A913FC0EA073E7B7D /* Pods_SimpleDemoCleanArchitecture.framework in Frameworks */,
103 | );
104 | runOnlyForDeploymentPostprocessing = 0;
105 | };
106 | /* End PBXFrameworksBuildPhase section */
107 |
108 | /* Begin PBXGroup section */
109 | 714C2EC0221F83AC001F98FD = {
110 | isa = PBXGroup;
111 | children = (
112 | 714C2ECB221F83AC001F98FD /* SimpleDemoCleanArchitecture */,
113 | 714C2ECA221F83AC001F98FD /* Products */,
114 | B31819743BD4361FAF650F86 /* Pods */,
115 | B2EC6C500E801CBB1BBAB9E5 /* Frameworks */,
116 | );
117 | sourceTree = "";
118 | };
119 | 714C2ECA221F83AC001F98FD /* Products */ = {
120 | isa = PBXGroup;
121 | children = (
122 | 714C2EC9221F83AC001F98FD /* SimpleDemoCleanArchitecture.app */,
123 | );
124 | name = Products;
125 | sourceTree = "";
126 | };
127 | 714C2ECB221F83AC001F98FD /* SimpleDemoCleanArchitecture */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 9E0BA0B52AD285BA00752733 /* README.md */,
131 | 714C2F09221F971E001F98FD /* Utils */,
132 | 714C2F23221FCD51001F98FD /* Extensions */,
133 | 714C2EF5221F91A9001F98FD /* Domain */,
134 | 714C2EF6221F91AE001F98FD /* Platform */,
135 | 714C2EDE221F868C001F98FD /* Scenes */,
136 | 714C2ECC221F83AC001F98FD /* AppDelegate.swift */,
137 | 714C2ED3221F83AE001F98FD /* Assets.xcassets */,
138 | 714C2ED5221F83AE001F98FD /* LaunchScreen.storyboard */,
139 | 714C2ED8221F83AE001F98FD /* Info.plist */,
140 | );
141 | path = SimpleDemoCleanArchitecture;
142 | sourceTree = "";
143 | };
144 | 714C2EDE221F868C001F98FD /* Scenes */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 714C2F28221FD739001F98FD /* RepoDetail */,
148 | 714C2EE9221F8999001F98FD /* Main */,
149 | 714C2EDF221F86A2001F98FD /* App */,
150 | 714C2EE6221F893C001F98FD /* StoryBoards */,
151 | );
152 | path = Scenes;
153 | sourceTree = "";
154 | };
155 | 714C2EDF221F86A2001F98FD /* App */ = {
156 | isa = PBXGroup;
157 | children = (
158 | 714C2EE0221F86B6001F98FD /* AppViewModel.swift */,
159 | 714C2EE2221F86C1001F98FD /* AppNavigator.swift */,
160 | 714C2EE4221F86D0001F98FD /* AppUseCase.swift */,
161 | );
162 | path = App;
163 | sourceTree = "";
164 | };
165 | 714C2EE6221F893C001F98FD /* StoryBoards */ = {
166 | isa = PBXGroup;
167 | children = (
168 | 714C2EE7221F8951001F98FD /* StoryBoards.swift */,
169 | 714C2ED0221F83AC001F98FD /* Main.storyboard */,
170 | );
171 | path = StoryBoards;
172 | sourceTree = "";
173 | };
174 | 714C2EE9221F8999001F98FD /* Main */ = {
175 | isa = PBXGroup;
176 | children = (
177 | 714C2F1E221FCA9E001F98FD /* Cell */,
178 | 714C2EED221F89C7001F98FD /* MainViewController.swift */,
179 | 714C2EF3221F8ED6001F98FD /* MainViewModel.swift */,
180 | 714C2EEF221F8EC1001F98FD /* MainNavigator.swift */,
181 | 714C2EF1221F8ECA001F98FD /* MainUseCase.swift */,
182 | );
183 | path = Main;
184 | sourceTree = "";
185 | };
186 | 714C2EF5221F91A9001F98FD /* Domain */ = {
187 | isa = PBXGroup;
188 | children = (
189 | 9E0BA0BF2AD28C2B00752733 /* Architecture */,
190 | 9E0BA0CB2AD28C2B00752733 /* Extensions */,
191 | 714C2EF7221F922A001F98FD /* Models */,
192 | );
193 | path = Domain;
194 | sourceTree = "";
195 | };
196 | 714C2EF6221F91AE001F98FD /* Platform */ = {
197 | isa = PBXGroup;
198 | children = (
199 | 714C2EFB221F92D9001F98FD /* Services */,
200 | 714C2EFA221F92C3001F98FD /* Repositories */,
201 | );
202 | path = Platform;
203 | sourceTree = "";
204 | };
205 | 714C2EF7221F922A001F98FD /* Models */ = {
206 | isa = PBXGroup;
207 | children = (
208 | 714C2EF8221F9238001F98FD /* GithubRepo.swift */,
209 | 9E0BA0E72AD28FF600752733 /* GithubOwner.swift */,
210 | );
211 | path = Models;
212 | sourceTree = "";
213 | };
214 | 714C2EFA221F92C3001F98FD /* Repositories */ = {
215 | isa = PBXGroup;
216 | children = (
217 | 714C2F1A221FC7D2001F98FD /* GithubRepoRepository.swift */,
218 | );
219 | path = Repositories;
220 | sourceTree = "";
221 | };
222 | 714C2EFB221F92D9001F98FD /* Services */ = {
223 | isa = PBXGroup;
224 | children = (
225 | 714C2F03221F94E0001F98FD /* API */,
226 | );
227 | path = Services;
228 | sourceTree = "";
229 | };
230 | 714C2F03221F94E0001F98FD /* API */ = {
231 | isa = PBXGroup;
232 | children = (
233 | 9E0BA0B82AD2876300752733 /* APITarget.swift */,
234 | 9E0BA0BA2AD287B200752733 /* APIService.swift */,
235 | 9E0BA0BC2AD2882500752733 /* Request */,
236 | 714C2F05221F96D5001F98FD /* Response */,
237 | );
238 | path = API;
239 | sourceTree = "";
240 | };
241 | 714C2F05221F96D5001F98FD /* Response */ = {
242 | isa = PBXGroup;
243 | children = (
244 | 714C2F12221F988A001F98FD /* GithubRepoResponse.swift */,
245 | );
246 | path = Response;
247 | sourceTree = "";
248 | };
249 | 714C2F09221F971E001F98FD /* Utils */ = {
250 | isa = PBXGroup;
251 | children = (
252 | 714C2F0E221F97F8001F98FD /* URLs.swift */,
253 | );
254 | path = Utils;
255 | sourceTree = "";
256 | };
257 | 714C2F1E221FCA9E001F98FD /* Cell */ = {
258 | isa = PBXGroup;
259 | children = (
260 | 714C2F20221FCAC6001F98FD /* GithubRepoCell.xib */,
261 | 714C2F1F221FCAC6001F98FD /* GithubRepoCell.swift */,
262 | );
263 | path = Cell;
264 | sourceTree = "";
265 | };
266 | 714C2F23221FCD51001F98FD /* Extensions */ = {
267 | isa = PBXGroup;
268 | children = (
269 | 714C2F24221FCD5D001F98FD /* ViewController+Rx.swift */,
270 | 714C2F26221FCDE2001F98FD /* UIViewController+.swift */,
271 | 9E0BA0CE2AD28C2B00752733 /* Array+.swift */,
272 | );
273 | path = Extensions;
274 | sourceTree = "";
275 | };
276 | 714C2F28221FD739001F98FD /* RepoDetail */ = {
277 | isa = PBXGroup;
278 | children = (
279 | 714C2F2F221FD791001F98FD /* RepoDetailViewController.swift */,
280 | 714C2F29221FD75F001F98FD /* RepoDetailViewModel.swift */,
281 | 714C2F2D221FD77D001F98FD /* RepoDetailNavigator.swift */,
282 | 714C2F2B221FD76E001F98FD /* RepoDetailUseCase.swift */,
283 | );
284 | path = RepoDetail;
285 | sourceTree = "";
286 | };
287 | 9E0BA0BC2AD2882500752733 /* Request */ = {
288 | isa = PBXGroup;
289 | children = (
290 | 9E0BA0BD2AD2882500752733 /* GithubRepoRequest.swift */,
291 | );
292 | path = Request;
293 | sourceTree = "";
294 | };
295 | 9E0BA0BF2AD28C2B00752733 /* Architecture */ = {
296 | isa = PBXGroup;
297 | children = (
298 | 9E0BA0E52AD28C8F00752733 /* ViewModelType.swift */,
299 | 9E0BA0C12AD28C2B00752733 /* BindableType.swift */,
300 | 9E0BA0C32AD28C2B00752733 /* ErrorTracker.swift */,
301 | 9E0BA0C42AD28C2B00752733 /* ActivityIndicator.swift */,
302 | 9E0BA0CA2AD28C2B00752733 /* MultiActivityIndicator.swift */,
303 | );
304 | path = Architecture;
305 | sourceTree = "";
306 | };
307 | 9E0BA0CB2AD28C2B00752733 /* Extensions */ = {
308 | isa = PBXGroup;
309 | children = (
310 | 9E0BA0CC2AD28C2B00752733 /* Driver+.swift */,
311 | 9E0BA0CD2AD28C2B00752733 /* ObservableType+.swift */,
312 | 9E0BA0D02AD28C2B00752733 /* UIViewController+Debug.swift */,
313 | );
314 | path = Extensions;
315 | sourceTree = "";
316 | };
317 | B2EC6C500E801CBB1BBAB9E5 /* Frameworks */ = {
318 | isa = PBXGroup;
319 | children = (
320 | A3A01E9CB332F807E2695B53 /* Pods_SimpleDemoCleanArchitecture.framework */,
321 | );
322 | name = Frameworks;
323 | sourceTree = "";
324 | };
325 | B31819743BD4361FAF650F86 /* Pods */ = {
326 | isa = PBXGroup;
327 | children = (
328 | 5BD36DC1B11198D43E3DE2E5 /* Pods-SimpleDemoCleanArchitecture.debug.xcconfig */,
329 | 37869304476713634AA3A366 /* Pods-SimpleDemoCleanArchitecture.release.xcconfig */,
330 | );
331 | name = Pods;
332 | sourceTree = "";
333 | };
334 | /* End PBXGroup section */
335 |
336 | /* Begin PBXNativeTarget section */
337 | 714C2EC8221F83AC001F98FD /* SimpleDemoCleanArchitecture */ = {
338 | isa = PBXNativeTarget;
339 | buildConfigurationList = 714C2EDB221F83AE001F98FD /* Build configuration list for PBXNativeTarget "SimpleDemoCleanArchitecture" */;
340 | buildPhases = (
341 | D35FB4CA8BCBC1697A8082AC /* [CP] Check Pods Manifest.lock */,
342 | 714C2EC5221F83AC001F98FD /* Sources */,
343 | 714C2EC6221F83AC001F98FD /* Frameworks */,
344 | 714C2EC7221F83AC001F98FD /* Resources */,
345 | FF1260FE5E85DC2F82C47BB7 /* [CP] Embed Pods Frameworks */,
346 | );
347 | buildRules = (
348 | );
349 | dependencies = (
350 | );
351 | name = SimpleDemoCleanArchitecture;
352 | productName = SimpleDemoCleanArchitecture;
353 | productReference = 714C2EC9221F83AC001F98FD /* SimpleDemoCleanArchitecture.app */;
354 | productType = "com.apple.product-type.application";
355 | };
356 | /* End PBXNativeTarget section */
357 |
358 | /* Begin PBXProject section */
359 | 714C2EC1221F83AC001F98FD /* Project object */ = {
360 | isa = PBXProject;
361 | attributes = {
362 | LastSwiftUpdateCheck = 1010;
363 | LastUpgradeCheck = 1010;
364 | ORGANIZATIONNAME = trinh.giang.dong;
365 | TargetAttributes = {
366 | 714C2EC8221F83AC001F98FD = {
367 | CreatedOnToolsVersion = 10.1;
368 | ProvisioningStyle = Automatic;
369 | };
370 | };
371 | };
372 | buildConfigurationList = 714C2EC4221F83AC001F98FD /* Build configuration list for PBXProject "SimpleDemoCleanArchitecture" */;
373 | compatibilityVersion = "Xcode 8.0";
374 | developmentRegion = en;
375 | hasScannedForEncodings = 0;
376 | knownRegions = (
377 | en,
378 | Base,
379 | );
380 | mainGroup = 714C2EC0221F83AC001F98FD;
381 | productRefGroup = 714C2ECA221F83AC001F98FD /* Products */;
382 | projectDirPath = "";
383 | projectRoot = "";
384 | targets = (
385 | 714C2EC8221F83AC001F98FD /* SimpleDemoCleanArchitecture */,
386 | );
387 | };
388 | /* End PBXProject section */
389 |
390 | /* Begin PBXResourcesBuildPhase section */
391 | 714C2EC7221F83AC001F98FD /* Resources */ = {
392 | isa = PBXResourcesBuildPhase;
393 | buildActionMask = 2147483647;
394 | files = (
395 | 714C2F22221FCAC6001F98FD /* GithubRepoCell.xib in Resources */,
396 | 714C2ED7221F83AE001F98FD /* LaunchScreen.storyboard in Resources */,
397 | 9E0BA0B62AD285BA00752733 /* README.md in Resources */,
398 | 714C2ED4221F83AE001F98FD /* Assets.xcassets in Resources */,
399 | 714C2ED2221F83AC001F98FD /* Main.storyboard in Resources */,
400 | );
401 | runOnlyForDeploymentPostprocessing = 0;
402 | };
403 | /* End PBXResourcesBuildPhase section */
404 |
405 | /* Begin PBXShellScriptBuildPhase section */
406 | D35FB4CA8BCBC1697A8082AC /* [CP] Check Pods Manifest.lock */ = {
407 | isa = PBXShellScriptBuildPhase;
408 | buildActionMask = 2147483647;
409 | files = (
410 | );
411 | inputFileListPaths = (
412 | );
413 | inputPaths = (
414 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
415 | "${PODS_ROOT}/Manifest.lock",
416 | );
417 | name = "[CP] Check Pods Manifest.lock";
418 | outputFileListPaths = (
419 | );
420 | outputPaths = (
421 | "$(DERIVED_FILE_DIR)/Pods-SimpleDemoCleanArchitecture-checkManifestLockResult.txt",
422 | );
423 | runOnlyForDeploymentPostprocessing = 0;
424 | shellPath = /bin/sh;
425 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
426 | showEnvVarsInLog = 0;
427 | };
428 | FF1260FE5E85DC2F82C47BB7 /* [CP] Embed Pods Frameworks */ = {
429 | isa = PBXShellScriptBuildPhase;
430 | buildActionMask = 2147483647;
431 | files = (
432 | );
433 | inputPaths = (
434 | "${PODS_ROOT}/Target Support Files/Pods-SimpleDemoCleanArchitecture/Pods-SimpleDemoCleanArchitecture-frameworks.sh",
435 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework",
436 | "${BUILT_PRODUCTS_DIR}/Differentiator/Differentiator.framework",
437 | "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework",
438 | "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework",
439 | "${BUILT_PRODUCTS_DIR}/Moya/Moya.framework",
440 | "${BUILT_PRODUCTS_DIR}/NSObject+Rx/NSObject_Rx.framework",
441 | "${BUILT_PRODUCTS_DIR}/Reusable/Reusable.framework",
442 | "${BUILT_PRODUCTS_DIR}/RxAppState/RxAppState.framework",
443 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework",
444 | "${BUILT_PRODUCTS_DIR}/RxDataSources/RxDataSources.framework",
445 | "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework",
446 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework",
447 | "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
448 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework",
449 | "${BUILT_PRODUCTS_DIR}/Then/Then.framework",
450 | );
451 | name = "[CP] Embed Pods Frameworks";
452 | outputPaths = (
453 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework",
454 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differentiator.framework",
455 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework",
456 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework",
457 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Moya.framework",
458 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NSObject_Rx.framework",
459 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reusable.framework",
460 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAppState.framework",
461 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework",
462 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxDataSources.framework",
463 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework",
464 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework",
465 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
466 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework",
467 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Then.framework",
468 | );
469 | runOnlyForDeploymentPostprocessing = 0;
470 | shellPath = /bin/sh;
471 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SimpleDemoCleanArchitecture/Pods-SimpleDemoCleanArchitecture-frameworks.sh\"\n";
472 | showEnvVarsInLog = 0;
473 | };
474 | /* End PBXShellScriptBuildPhase section */
475 |
476 | /* Begin PBXSourcesBuildPhase section */
477 | 714C2EC5221F83AC001F98FD /* Sources */ = {
478 | isa = PBXSourcesBuildPhase;
479 | buildActionMask = 2147483647;
480 | files = (
481 | 714C2EEE221F89C7001F98FD /* MainViewController.swift in Sources */,
482 | 714C2EE8221F8951001F98FD /* StoryBoards.swift in Sources */,
483 | 714C2F0F221F97F8001F98FD /* URLs.swift in Sources */,
484 | 9E0BA0DE2AD28C2B00752733 /* Driver+.swift in Sources */,
485 | 9E0BA0E02AD28C2B00752733 /* Array+.swift in Sources */,
486 | 714C2F2C221FD76E001F98FD /* RepoDetailUseCase.swift in Sources */,
487 | 9E0BA0D72AD28C2B00752733 /* ActivityIndicator.swift in Sources */,
488 | 9E0BA0BE2AD2882500752733 /* GithubRepoRequest.swift in Sources */,
489 | 9E0BA0DD2AD28C2B00752733 /* MultiActivityIndicator.swift in Sources */,
490 | 714C2EF9221F9238001F98FD /* GithubRepo.swift in Sources */,
491 | 714C2F1B221FC7D2001F98FD /* GithubRepoRepository.swift in Sources */,
492 | 714C2F2E221FD77D001F98FD /* RepoDetailNavigator.swift in Sources */,
493 | 9E0BA0E62AD28C8F00752733 /* ViewModelType.swift in Sources */,
494 | 714C2EE3221F86C1001F98FD /* AppNavigator.swift in Sources */,
495 | 714C2EE5221F86D0001F98FD /* AppUseCase.swift in Sources */,
496 | 714C2F30221FD791001F98FD /* RepoDetailViewController.swift in Sources */,
497 | 714C2F2A221FD75F001F98FD /* RepoDetailViewModel.swift in Sources */,
498 | 714C2F25221FCD5D001F98FD /* ViewController+Rx.swift in Sources */,
499 | 9E0BA0E82AD28FF600752733 /* GithubOwner.swift in Sources */,
500 | 9E0BA0D42AD28C2B00752733 /* BindableType.swift in Sources */,
501 | 714C2EF4221F8ED6001F98FD /* MainViewModel.swift in Sources */,
502 | 714C2ECD221F83AC001F98FD /* AppDelegate.swift in Sources */,
503 | 9E0BA0E22AD28C2B00752733 /* UIViewController+Debug.swift in Sources */,
504 | 9E0BA0B92AD2876300752733 /* APITarget.swift in Sources */,
505 | 714C2F21221FCAC6001F98FD /* GithubRepoCell.swift in Sources */,
506 | 714C2F13221F988A001F98FD /* GithubRepoResponse.swift in Sources */,
507 | 714C2EF0221F8EC1001F98FD /* MainNavigator.swift in Sources */,
508 | 714C2F27221FCDE2001F98FD /* UIViewController+.swift in Sources */,
509 | 9E0BA0D62AD28C2B00752733 /* ErrorTracker.swift in Sources */,
510 | 714C2EE1221F86B6001F98FD /* AppViewModel.swift in Sources */,
511 | 9E0BA0DF2AD28C2B00752733 /* ObservableType+.swift in Sources */,
512 | 9E0BA0BB2AD287B200752733 /* APIService.swift in Sources */,
513 | 714C2EF2221F8ECA001F98FD /* MainUseCase.swift in Sources */,
514 | );
515 | runOnlyForDeploymentPostprocessing = 0;
516 | };
517 | /* End PBXSourcesBuildPhase section */
518 |
519 | /* Begin PBXVariantGroup section */
520 | 714C2ED0221F83AC001F98FD /* Main.storyboard */ = {
521 | isa = PBXVariantGroup;
522 | children = (
523 | 714C2ED1221F83AC001F98FD /* Base */,
524 | );
525 | name = Main.storyboard;
526 | sourceTree = "";
527 | };
528 | 714C2ED5221F83AE001F98FD /* LaunchScreen.storyboard */ = {
529 | isa = PBXVariantGroup;
530 | children = (
531 | 714C2ED6221F83AE001F98FD /* Base */,
532 | );
533 | name = LaunchScreen.storyboard;
534 | sourceTree = "";
535 | };
536 | /* End PBXVariantGroup section */
537 |
538 | /* Begin XCBuildConfiguration section */
539 | 714C2ED9221F83AE001F98FD /* Debug */ = {
540 | isa = XCBuildConfiguration;
541 | buildSettings = {
542 | ALWAYS_SEARCH_USER_PATHS = NO;
543 | CLANG_ANALYZER_NONNULL = YES;
544 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
545 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
546 | CLANG_CXX_LIBRARY = "libc++";
547 | CLANG_ENABLE_MODULES = YES;
548 | CLANG_ENABLE_OBJC_ARC = YES;
549 | CLANG_ENABLE_OBJC_WEAK = YES;
550 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
551 | CLANG_WARN_BOOL_CONVERSION = YES;
552 | CLANG_WARN_COMMA = YES;
553 | CLANG_WARN_CONSTANT_CONVERSION = YES;
554 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
555 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
556 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
557 | CLANG_WARN_EMPTY_BODY = YES;
558 | CLANG_WARN_ENUM_CONVERSION = YES;
559 | CLANG_WARN_INFINITE_RECURSION = YES;
560 | CLANG_WARN_INT_CONVERSION = YES;
561 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
562 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
563 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
564 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
565 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
566 | CLANG_WARN_STRICT_PROTOTYPES = YES;
567 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
568 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
569 | CLANG_WARN_UNREACHABLE_CODE = YES;
570 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
571 | CODE_SIGN_IDENTITY = "iPhone Developer";
572 | COPY_PHASE_STRIP = NO;
573 | DEBUG_INFORMATION_FORMAT = dwarf;
574 | ENABLE_STRICT_OBJC_MSGSEND = YES;
575 | ENABLE_TESTABILITY = YES;
576 | GCC_C_LANGUAGE_STANDARD = gnu11;
577 | GCC_DYNAMIC_NO_PIC = NO;
578 | GCC_NO_COMMON_BLOCKS = YES;
579 | GCC_OPTIMIZATION_LEVEL = 0;
580 | GCC_PREPROCESSOR_DEFINITIONS = (
581 | "DEBUG=1",
582 | "$(inherited)",
583 | );
584 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
585 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
586 | GCC_WARN_UNDECLARED_SELECTOR = YES;
587 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
588 | GCC_WARN_UNUSED_FUNCTION = YES;
589 | GCC_WARN_UNUSED_VARIABLE = YES;
590 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
591 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
592 | MTL_FAST_MATH = YES;
593 | ONLY_ACTIVE_ARCH = YES;
594 | SDKROOT = iphoneos;
595 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
596 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
597 | };
598 | name = Debug;
599 | };
600 | 714C2EDA221F83AE001F98FD /* Release */ = {
601 | isa = XCBuildConfiguration;
602 | buildSettings = {
603 | ALWAYS_SEARCH_USER_PATHS = NO;
604 | CLANG_ANALYZER_NONNULL = YES;
605 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
606 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
607 | CLANG_CXX_LIBRARY = "libc++";
608 | CLANG_ENABLE_MODULES = YES;
609 | CLANG_ENABLE_OBJC_ARC = YES;
610 | CLANG_ENABLE_OBJC_WEAK = YES;
611 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
612 | CLANG_WARN_BOOL_CONVERSION = YES;
613 | CLANG_WARN_COMMA = YES;
614 | CLANG_WARN_CONSTANT_CONVERSION = YES;
615 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
616 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
617 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
618 | CLANG_WARN_EMPTY_BODY = YES;
619 | CLANG_WARN_ENUM_CONVERSION = YES;
620 | CLANG_WARN_INFINITE_RECURSION = YES;
621 | CLANG_WARN_INT_CONVERSION = YES;
622 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
623 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
624 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
625 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
626 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
627 | CLANG_WARN_STRICT_PROTOTYPES = YES;
628 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
629 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
630 | CLANG_WARN_UNREACHABLE_CODE = YES;
631 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
632 | CODE_SIGN_IDENTITY = "iPhone Developer";
633 | COPY_PHASE_STRIP = NO;
634 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
635 | ENABLE_NS_ASSERTIONS = NO;
636 | ENABLE_STRICT_OBJC_MSGSEND = YES;
637 | GCC_C_LANGUAGE_STANDARD = gnu11;
638 | GCC_NO_COMMON_BLOCKS = YES;
639 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
640 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
641 | GCC_WARN_UNDECLARED_SELECTOR = YES;
642 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
643 | GCC_WARN_UNUSED_FUNCTION = YES;
644 | GCC_WARN_UNUSED_VARIABLE = YES;
645 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
646 | MTL_ENABLE_DEBUG_INFO = NO;
647 | MTL_FAST_MATH = YES;
648 | SDKROOT = iphoneos;
649 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
650 | VALIDATE_PRODUCT = YES;
651 | };
652 | name = Release;
653 | };
654 | 714C2EDC221F83AE001F98FD /* Debug */ = {
655 | isa = XCBuildConfiguration;
656 | baseConfigurationReference = 5BD36DC1B11198D43E3DE2E5 /* Pods-SimpleDemoCleanArchitecture.debug.xcconfig */;
657 | buildSettings = {
658 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
659 | CODE_SIGN_STYLE = Automatic;
660 | INFOPLIST_FILE = SimpleDemoCleanArchitecture/Info.plist;
661 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
662 | PRODUCT_BUNDLE_IDENTIFIER = com.example.SimpleDemoCleanArchitecture;
663 | PRODUCT_NAME = "$(TARGET_NAME)";
664 | SWIFT_VERSION = 5.0;
665 | TARGETED_DEVICE_FAMILY = "1,2";
666 | };
667 | name = Debug;
668 | };
669 | 714C2EDD221F83AE001F98FD /* Release */ = {
670 | isa = XCBuildConfiguration;
671 | baseConfigurationReference = 37869304476713634AA3A366 /* Pods-SimpleDemoCleanArchitecture.release.xcconfig */;
672 | buildSettings = {
673 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
674 | CODE_SIGN_STYLE = Automatic;
675 | INFOPLIST_FILE = SimpleDemoCleanArchitecture/Info.plist;
676 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
677 | PRODUCT_BUNDLE_IDENTIFIER = com.example.SimpleDemoCleanArchitecture;
678 | PRODUCT_NAME = "$(TARGET_NAME)";
679 | SWIFT_VERSION = 5.0;
680 | TARGETED_DEVICE_FAMILY = "1,2";
681 | };
682 | name = Release;
683 | };
684 | /* End XCBuildConfiguration section */
685 |
686 | /* Begin XCConfigurationList section */
687 | 714C2EC4221F83AC001F98FD /* Build configuration list for PBXProject "SimpleDemoCleanArchitecture" */ = {
688 | isa = XCConfigurationList;
689 | buildConfigurations = (
690 | 714C2ED9221F83AE001F98FD /* Debug */,
691 | 714C2EDA221F83AE001F98FD /* Release */,
692 | );
693 | defaultConfigurationIsVisible = 0;
694 | defaultConfigurationName = Release;
695 | };
696 | 714C2EDB221F83AE001F98FD /* Build configuration list for PBXNativeTarget "SimpleDemoCleanArchitecture" */ = {
697 | isa = XCConfigurationList;
698 | buildConfigurations = (
699 | 714C2EDC221F83AE001F98FD /* Debug */,
700 | 714C2EDD221F83AE001F98FD /* Release */,
701 | );
702 | defaultConfigurationIsVisible = 0;
703 | defaultConfigurationName = Release;
704 | };
705 | /* End XCConfigurationList section */
706 | };
707 | rootObject = 714C2EC1221F83AC001F98FD /* Project object */;
708 | }
709 |
--------------------------------------------------------------------------------