├── .github └── workflows │ └── build.yml ├── .gitignore ├── Examples ├── Example-iOS-RAC │ ├── .gitignore │ ├── Boundary │ │ ├── Components │ │ │ ├── Home │ │ │ │ └── HomeUseCase.swift │ │ │ ├── Login │ │ │ │ └── LoginUseCase.swift │ │ │ ├── MyPage │ │ │ │ └── MyPageUseCase.swift │ │ │ └── User │ │ │ │ └── Edit │ │ │ │ └── UserEditUseCase.swift │ │ ├── Foundations │ │ │ └── Refreshable.swift │ │ └── Info.plist │ ├── Cartfile │ ├── Cartfile.resolved │ ├── Catalog │ │ ├── AppDelegate.swift │ │ ├── Configs │ │ │ ├── Base.xcconfig │ │ │ ├── Dev.xcconfig │ │ │ ├── Prd.xcconfig │ │ │ ├── Provisioning │ │ │ │ ├── Development.xcconfig │ │ │ │ └── Distribution.xcconfig │ │ │ ├── Stg.xcconfig │ │ │ └── Test.xcconfig │ │ ├── Mocks │ │ │ ├── Components │ │ │ │ ├── EntranceViewController+Mock.swift │ │ │ │ ├── HomeHeaderView+Mock.swift │ │ │ │ ├── HomeViewController+Mock.swift │ │ │ │ ├── LoginViewController+Mock.swift │ │ │ │ ├── MyPageLabelView+Mock.swift │ │ │ │ ├── MyPageUserView+Mock.swift │ │ │ │ ├── MyPageViewController+Mock.swift │ │ │ │ └── UserEditViewController+Mock.swift │ │ │ └── Foundations │ │ │ │ └── UserStateNotificationMock.swift │ │ ├── Resources │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj │ │ │ │ └── LaunchScreen.storyboard │ │ │ └── Info.plist │ │ ├── Scenarios │ │ │ ├── AllScenarios.swift │ │ │ ├── EntranceScenarios.swift │ │ │ ├── HomeScenarios.swift │ │ │ ├── LoginScenarios.swift │ │ │ ├── MyPageScenarios.swift │ │ │ └── UserEditScenarios.swift │ │ └── SceneDelegate.swift │ ├── CatalogSnapshot │ │ ├── Info.plist │ │ └── SnapshotTests.swift │ ├── Component │ │ ├── Components │ │ │ ├── Entrance │ │ │ │ ├── EntranceViewController.swift │ │ │ │ └── EntranceViewController.xib │ │ │ ├── Home │ │ │ │ ├── HomeComposer.swift │ │ │ │ ├── HomeViewController.swift │ │ │ │ ├── HomeViewController.xib │ │ │ │ ├── HomeViewModel.swift │ │ │ │ └── Views │ │ │ │ │ ├── HomeHeaderView.swift │ │ │ │ │ └── HomeHeaderView.xib │ │ │ ├── Login │ │ │ │ ├── LoginViewController.swift │ │ │ │ ├── LoginViewController.xib │ │ │ │ └── LoginViewModel.swift │ │ │ ├── MyPage │ │ │ │ ├── Entities │ │ │ │ │ └── MyPageItem.swift │ │ │ │ ├── MyPageComposer.swift │ │ │ │ ├── MyPageViewController.swift │ │ │ │ ├── MyPageViewController.xib │ │ │ │ ├── MyPageViewModel.swift │ │ │ │ └── Views │ │ │ │ │ ├── MyPageLabelView.swift │ │ │ │ │ ├── MyPageLabelView.xib │ │ │ │ │ ├── MyPageUserView.swift │ │ │ │ │ └── MyPageUserView.xib │ │ │ ├── NavigationController.swift │ │ │ ├── RootViewController.swift │ │ │ ├── RootViewController.xib │ │ │ ├── TabBarController.swift │ │ │ └── User │ │ │ │ └── Edit │ │ │ │ ├── UserEditViewController.swift │ │ │ │ ├── UserEditViewController.xib │ │ │ │ └── UserEditViewModel.swift │ │ ├── Extensions │ │ │ ├── Bundle+Token.swift │ │ │ ├── Optional+Utility.swift │ │ │ ├── Reactive │ │ │ │ ├── Refreshable+Reactive.swift │ │ │ │ └── UIControl+Reactive.swift │ │ │ └── UIKit │ │ │ │ ├── UITableView+Utility.swift │ │ │ │ └── UIViewController+Utility.swift │ │ ├── Foundations │ │ │ ├── LoadProgress.swift │ │ │ ├── NibBundler.swift │ │ │ ├── NibLoadable.swift │ │ │ ├── Notification │ │ │ │ └── UserStateNotification.swift │ │ │ └── Renderable.swift │ │ ├── Loading │ │ │ ├── LoadingViewController.swift │ │ │ └── LoadingViewController.xib │ │ └── Resources │ │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── User.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── User.pdf │ │ │ └── Info.plist │ ├── Configs │ │ ├── Base.xcconfig │ │ ├── Dev.xcconfig │ │ ├── Prd.xcconfig │ │ ├── Stg.xcconfig │ │ └── Test.xcconfig │ ├── Entity │ │ ├── Info.plist │ │ └── User.swift │ ├── EntityTests │ │ └── Info.plist │ ├── Example-iOS-RAC │ │ ├── AppDelegate.swift │ │ ├── Configs │ │ │ ├── Base.xcconfig │ │ │ ├── Dev.xcconfig │ │ │ ├── Environment │ │ │ │ ├── Dev.xcconfig │ │ │ │ ├── Prd.xcconfig │ │ │ │ └── Stg.xcconfig │ │ │ ├── Prd.xcconfig │ │ │ ├── Provisioning │ │ │ │ ├── Development.xcconfig │ │ │ │ └── Distribution.xcconfig │ │ │ ├── Stg.xcconfig │ │ │ └── Test.xcconfig │ │ ├── Extensions │ │ │ ├── Bundle+Info.swift │ │ │ ├── Gateway │ │ │ │ └── Repositories │ │ │ │ │ └── UserRepository.Dependency+DI.swift │ │ │ ├── Notification │ │ │ │ └── NotificationCenter+UserState.swift │ │ │ ├── UIKit │ │ │ │ └── UITabBarController+Utility.swift │ │ │ └── UseCase │ │ │ │ └── Interactors │ │ │ │ ├── HomeInteractor.Dependency+DI.swift │ │ │ │ ├── LoginInteractor.Dependency+DI.swift │ │ │ │ ├── MyPageInteractor.Dependency+DI.swift │ │ │ │ └── UserEditInteractor.Dependency+DI.swift │ │ ├── Resources │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj │ │ │ │ └── LaunchScreen.storyboard │ │ │ └── Info.plist │ │ └── Router │ │ │ ├── AppRouter.swift │ │ │ ├── NavigationRouter.swift │ │ │ ├── RootRouter.swift │ │ │ └── TabRouter.swift │ ├── Gateway │ │ ├── Foundations │ │ │ ├── DefaultsKey.swift │ │ │ └── UUIDGen.swift │ │ ├── Info.plist │ │ └── Repositories │ │ │ └── UserRepository.swift │ ├── GatewayTests │ │ └── Info.plist │ ├── Makefile │ ├── Presentation │ │ ├── Components │ │ │ ├── Home │ │ │ │ └── HomePresenter.swift │ │ │ ├── Login │ │ │ │ └── LoginPresenter.swift │ │ │ ├── MyPage │ │ │ │ └── MyPagePresenter.swift │ │ │ └── User │ │ │ │ └── Edit │ │ │ │ └── UserEditPresenter.swift │ │ ├── Extensions │ │ │ └── Translator+Executor.swift │ │ └── Info.plist │ ├── PresentationTests │ │ └── Info.plist │ ├── README.md │ ├── UseCase │ │ ├── Components │ │ │ ├── Home │ │ │ │ └── HomeInteractor.swift │ │ │ ├── Login │ │ │ │ └── LoginInteractor.swift │ │ │ ├── MyPage │ │ │ │ └── MyPageInteractor.swift │ │ │ └── User │ │ │ │ └── Edit │ │ │ │ └── UserEditInteractor.swift │ │ ├── Foundations │ │ │ └── ScopedSerialDisposable.swift │ │ ├── Info.plist │ │ └── Repositories │ │ │ └── UserRepository.swift │ ├── UseCaseTests │ │ └── Info.plist │ └── project.yml ├── Example-iOS-Rx │ ├── .gitignore │ ├── Boundary │ │ ├── Components │ │ │ ├── Home │ │ │ │ └── HomeUseCase.swift │ │ │ ├── Login │ │ │ │ └── LoginUseCase.swift │ │ │ ├── MyPage │ │ │ │ └── MyPageUseCase.swift │ │ │ └── User │ │ │ │ └── Edit │ │ │ │ └── UserEditUseCase.swift │ │ ├── Foundations │ │ │ └── Refreshable.swift │ │ └── Info.plist │ ├── Cartfile │ ├── Cartfile.resolved │ ├── Catalog │ │ ├── AppDelegate.swift │ │ ├── Configs │ │ │ ├── Base.xcconfig │ │ │ ├── Dev.xcconfig │ │ │ ├── Prd.xcconfig │ │ │ ├── Provisioning │ │ │ │ ├── Development.xcconfig │ │ │ │ └── Distribution.xcconfig │ │ │ ├── Stg.xcconfig │ │ │ └── Test.xcconfig │ │ ├── Mocks │ │ │ ├── Components │ │ │ │ ├── EntranceViewController+Mock.swift │ │ │ │ ├── HomeHeaderView+Mock.swift │ │ │ │ ├── HomeViewController+Mock.swift │ │ │ │ ├── LoginViewController+Mock.swift │ │ │ │ ├── MyPageLabelView+Mock.swift │ │ │ │ ├── MyPageUserView+Mock.swift │ │ │ │ ├── MyPageViewController+Mock.swift │ │ │ │ └── UserEditViewController+Mock.swift │ │ │ └── Foundations │ │ │ │ └── UserStateNotificationMock.swift │ │ ├── Resources │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj │ │ │ │ └── LaunchScreen.storyboard │ │ │ └── Info.plist │ │ ├── Scenarios │ │ │ ├── AllScenarios.swift │ │ │ ├── EntranceScenarios.swift │ │ │ ├── HomeScenarios.swift │ │ │ ├── LoginScenarios.swift │ │ │ ├── MyPageScenarios.swift │ │ │ └── UserEditScenarios.swift │ │ └── SceneDelegate.swift │ ├── CatalogSnapshot │ │ ├── Info.plist │ │ └── SnapshotTests.swift │ ├── Component │ │ ├── Components │ │ │ ├── Entrance │ │ │ │ ├── EntranceViewController.swift │ │ │ │ └── EntranceViewController.xib │ │ │ ├── Home │ │ │ │ ├── HomeComposer.swift │ │ │ │ ├── HomeViewController.swift │ │ │ │ ├── HomeViewController.xib │ │ │ │ ├── HomeViewModel.swift │ │ │ │ └── Views │ │ │ │ │ ├── HomeHeaderView.swift │ │ │ │ │ └── HomeHeaderView.xib │ │ │ ├── Login │ │ │ │ ├── LoginViewController.swift │ │ │ │ ├── LoginViewController.xib │ │ │ │ └── LoginViewModel.swift │ │ │ ├── MyPage │ │ │ │ ├── Entities │ │ │ │ │ └── MyPageItem.swift │ │ │ │ ├── MyPageComposer.swift │ │ │ │ ├── MyPageViewController.swift │ │ │ │ ├── MyPageViewController.xib │ │ │ │ ├── MyPageViewModel.swift │ │ │ │ └── Views │ │ │ │ │ ├── MyPageLabelView.swift │ │ │ │ │ ├── MyPageLabelView.xib │ │ │ │ │ ├── MyPageUserView.swift │ │ │ │ │ └── MyPageUserView.xib │ │ │ ├── NavigationController.swift │ │ │ ├── RootViewController.swift │ │ │ ├── RootViewController.xib │ │ │ ├── TabBarController.swift │ │ │ └── User │ │ │ │ └── Edit │ │ │ │ ├── UserEditViewController.swift │ │ │ │ ├── UserEditViewController.xib │ │ │ │ └── UserEditViewModel.swift │ │ ├── Extensions │ │ │ ├── Bundle+Token.swift │ │ │ ├── Optional+Utility.swift │ │ │ ├── Rx │ │ │ │ ├── ObservableType+Operator.swift │ │ │ │ ├── Refreshable+Rx.swift │ │ │ │ └── UIControl+Rx.swift │ │ │ └── UIKit │ │ │ │ ├── UITableView+Utility.swift │ │ │ │ └── UIViewController+Utility.swift │ │ ├── Foundations │ │ │ ├── LoadProgress.swift │ │ │ ├── NibBundler.swift │ │ │ ├── NibLoadable.swift │ │ │ ├── Notification │ │ │ │ └── UserStateNotification.swift │ │ │ └── Renderable.swift │ │ ├── Loading │ │ │ ├── LoadingViewController.swift │ │ │ └── LoadingViewController.xib │ │ └── Resources │ │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── User.imageset │ │ │ │ ├── Contents.json │ │ │ │ └── User.pdf │ │ │ └── Info.plist │ ├── Configs │ │ ├── Base.xcconfig │ │ ├── Dev.xcconfig │ │ ├── Prd.xcconfig │ │ ├── Stg.xcconfig │ │ └── Test.xcconfig │ ├── Entity │ │ ├── Info.plist │ │ └── User.swift │ ├── EntityTests │ │ └── Info.plist │ ├── Example-iOS-Rx │ │ ├── AppDelegate.swift │ │ ├── Configs │ │ │ ├── Base.xcconfig │ │ │ ├── Dev.xcconfig │ │ │ ├── Environment │ │ │ │ ├── Dev.xcconfig │ │ │ │ ├── Prd.xcconfig │ │ │ │ └── Stg.xcconfig │ │ │ ├── Prd.xcconfig │ │ │ ├── Provisioning │ │ │ │ ├── Development.xcconfig │ │ │ │ └── Distribution.xcconfig │ │ │ ├── Stg.xcconfig │ │ │ └── Test.xcconfig │ │ ├── Extensions │ │ │ ├── Bundle+AppInfo.swift │ │ │ ├── Gateway │ │ │ │ └── Repositories │ │ │ │ │ └── UserRepository.Dependency+DI.swift │ │ │ ├── Notification │ │ │ │ └── NotificationCenter+UserState.swift │ │ │ ├── UIKit │ │ │ │ └── UITabBarController+Utility.swift │ │ │ └── UseCase │ │ │ │ └── Interactors │ │ │ │ ├── HomeInteractor.Dependency+DI.swift │ │ │ │ ├── LoginInteractor.Dependency+DI.swift │ │ │ │ ├── MyPageInteractor.Dependency+DI.swift │ │ │ │ └── UserEditInteractor.Dependency+DI.swift │ │ ├── Resources │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Base.lproj │ │ │ │ └── LaunchScreen.storyboard │ │ │ └── Info.plist │ │ └── Router │ │ │ ├── AppRouter.swift │ │ │ ├── NavigationRouter.swift │ │ │ ├── RootRouter.swift │ │ │ └── TabRouter.swift │ ├── Gateway │ │ ├── Foundations │ │ │ ├── DefaultsKey.swift │ │ │ └── UUIDGen.swift │ │ ├── Info.plist │ │ └── Repositories │ │ │ └── UserRepository.swift │ ├── GatewayTests │ │ └── Info.plist │ ├── Makefile │ ├── Presentation │ │ ├── Components │ │ │ ├── Home │ │ │ │ └── HomePresenter.swift │ │ │ ├── Login │ │ │ │ └── LoginPresenter.swift │ │ │ ├── MyPage │ │ │ │ └── MyPagePresenter.swift │ │ │ └── User │ │ │ │ └── Edit │ │ │ │ └── UserEditPresenter.swift │ │ ├── Extensions │ │ │ ├── Rx │ │ │ │ └── BehaviorRelay+Modify.swift │ │ │ └── Translator+Executor.swift │ │ └── Info.plist │ ├── PresentationTests │ │ └── Info.plist │ ├── README.md │ ├── UseCase │ │ ├── Components │ │ │ ├── Home │ │ │ │ └── HomeInteractor.swift │ │ │ ├── Login │ │ │ │ └── LoginInteractor.swift │ │ │ ├── MyPage │ │ │ │ └── MyPageInteractor.swift │ │ │ └── User │ │ │ │ └── Edit │ │ │ │ └── UserEditInteractor.swift │ │ ├── Info.plist │ │ └── Repositories │ │ │ └── UserRepository.swift │ ├── UseCaseTests │ │ └── Info.plist │ └── project.yml ├── README.md └── plan.pu ├── LICENSE ├── Package.swift ├── Plan.podspec ├── Plan.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── Plan.xcscheme ├── Plan ├── Atomic.swift ├── Dispatchable.swift ├── Dispatcher.swift ├── Executor.swift ├── Info.plist ├── Interactor.swift ├── Plan.h ├── Presenter.swift ├── Queues │ ├── ImmediateQueue.swift │ ├── MainThreadQueue.swift │ └── Queue.swift ├── Store.swift └── Translator.swift ├── PlanTests ├── AtomicTests.swift ├── DispatcherTests.swift ├── ExecutorTests.swift ├── Info.plist ├── InteractorTests.swift ├── Mocks │ ├── DispatcherMock.swift │ ├── MockAction.swift │ └── TranslatorMock.swift ├── PresenterTests.swift └── TranslatorTests.swift ├── README.md ├── Support Files └── Plan.xcconfig ├── codecov.yml └── fastlane ├── Appfile ├── Fastfile └── README.md /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: macOS-latest 6 | steps: 7 | - uses: actions/checkout@v1 8 | - name: Run fastlane tests 9 | run: fastlane run_project_tests 10 | - name: Update Code Coverage 11 | run: bash <(curl -s https://codecov.io/bash) -J 'Plan' 12 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Boundary/Components/Home/HomeUseCase.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | 3 | public enum HomeUseCaseAction { 4 | case updateUser(User?) 5 | } 6 | 7 | public protocol HomeUseCase: Refreshable {} 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Boundary/Components/Login/LoginUseCase.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | 3 | public enum LoginUseCaseAction { 4 | case loading 5 | case login(Result) 6 | } 7 | 8 | public protocol LoginUseCase { 9 | func login(userName: String, password: String) 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Boundary/Components/MyPage/MyPageUseCase.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | 3 | public enum MyPageUseCaseAction { 4 | case updateUser(User?) 5 | case logout 6 | } 7 | 8 | public protocol MyPageUseCase: Refreshable { 9 | func logout() 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Boundary/Components/User/Edit/UserEditUseCase.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | 3 | public enum UserEditUseCaseAction { 4 | case loading 5 | case edit(Result) 6 | } 7 | 8 | public protocol UserEditUseCase { 9 | func edit(userName: String, password: String) 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Boundary/Foundations/Refreshable.swift: -------------------------------------------------------------------------------- 1 | import ReactiveSwift 2 | 3 | public protocol Refreshable: AnyObject, ReactiveExtensionsProvider { 4 | func refresh() 5 | } 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Boundary/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveCocoa/ReactiveCocoa" == 10.3.0 2 | github "ReactiveCocoa/ReactiveSwift" == 6.3.0 3 | github "ra1028/Carbon" "fbe2c84c0e006051a8592624be6c32f87a10cd83" 4 | git "../../../Plan" == 0.2.0 5 | github "playbook-ui/playbook-ios" -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Cartfile.resolved: -------------------------------------------------------------------------------- 1 | git "../../../Plan" "0.2.0" 2 | github "ReactiveCocoa/ReactiveCocoa" "10.3.0" 3 | github "ReactiveCocoa/ReactiveSwift" "6.3.0" 4 | github "playbook-ui/playbook-ios" "0.1.1" 5 | github "ra1028/Carbon" "fbe2c84c0e006051a8592624be6c32f87a10cd83" 6 | github "ra1028/DifferenceKit" "1.1.5" 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | final class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 6 | UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Configs/Base.xcconfig: -------------------------------------------------------------------------------- 1 | TARGETED_DEVICE_FAMILY = 1 2 | SDKROOT = iphoneos 3 | SUPPORTED_PLATFORMS = iphonesimulator iphoneos 4 | DEVELOPMENT_LANGUAGE = Japan 5 | IPHONEOS_DEPLOYMENT_TARGET = 13.0 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Configs/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Provisioning/Development.xcconfig" 3 | //:configuration = Dev 4 | 5 | PRODUCT_DISPLAY_NAME = Catalog-Dev 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Configs/Prd.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Provisioning/Distribution.xcconfig" 3 | //:configuration = Prd 4 | 5 | PRODUCT_DISPLAY_NAME = Catalog 6 | CLANG_ENABLE_CODE_COVERAGE = NO 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Configs/Provisioning/Development.xcconfig: -------------------------------------------------------------------------------- 1 | CODE_SIGN_IDENTITY = iPhone Developer 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Configs/Provisioning/Distribution.xcconfig: -------------------------------------------------------------------------------- 1 | CODE_SIGN_IDENTITY = iPhone Distribution 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Configs/Stg.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Provisioning/Development.xcconfig" 3 | //:configuration = Stg 4 | 5 | PRODUCT_DISPLAY_NAME = Catalog-Stg 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Configs/Test.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Provisioning/Development.xcconfig" 3 | //:configuration = Test 4 | 5 | PRODUCT_DISPLAY_NAME = Catalog-Test 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Components/EntranceViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | 3 | extension EntranceViewController { 4 | static func mock() -> EntranceViewController { 5 | EntranceViewController(router: EntranceRouterMock()) 6 | } 7 | } 8 | 9 | final class EntranceRouterMock: EntranceRouting { 10 | func showHome() {} 11 | func showMyPage() {} 12 | } 13 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Components/HomeHeaderView+Mock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import Entity 3 | 4 | extension HomeHeaderView { 5 | static func mock(user: User?) -> HomeHeaderView { 6 | let view = HomeHeaderView.loadFromNib() 7 | view.render(with: .init(user: user)) 8 | return view 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Components/HomeViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Entity 4 | import ReactiveSwift 5 | 6 | extension HomeViewController { 7 | static func mock(user: User?) -> HomeViewController { 8 | HomeViewController( 9 | presenter: HomePresenterMock(user: user), 10 | useCase: HomeUseCaseMock(), 11 | router: HomeRouterMock(), 12 | notification: UserStateNotificationMock() 13 | ) 14 | } 15 | } 16 | 17 | final class HomePresenterMock: HomePresenterProtocol { 18 | let dataState: Property 19 | 20 | init(user: User?) { 21 | dataState = .init(value: .init(user: user)) 22 | } 23 | } 24 | 25 | final class HomeUseCaseMock: HomeUseCase { 26 | func refresh() {} 27 | } 28 | 29 | final class HomeRouterMock: HomeRouting {} 30 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Components/LoginViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import ReactiveSwift 4 | 5 | extension LoginViewController { 6 | static func mock() -> LoginViewController { 7 | LoginViewController( 8 | presenter: LoginPresenterMock(), 9 | useCase: LoginUseCaseMock(), 10 | router: LoginRouterMock(), 11 | notification: UserStateNotificationMock() 12 | ) 13 | } 14 | } 15 | 16 | final class LoginPresenterMock: LoginPresenterProtocol { 17 | let viewModel: Property = .init(value: .init()) 18 | let command: Signal = .empty 19 | } 20 | 21 | final class LoginUseCaseMock: LoginUseCase { 22 | func login(userName: String, password: String) {} 23 | } 24 | 25 | final class LoginRouterMock: LoginRouting { 26 | func dismiss() {} 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Components/MyPageLabelView+Mock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | 3 | extension MyPageLabelView { 4 | static func mock(text: String) -> MyPageLabelView { 5 | let view = MyPageLabelView.loadFromNib() 6 | view.render(with: .init(text: text, tap: {})) 7 | return view 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Components/MyPageUserView+Mock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import Entity 3 | 4 | extension MyPageUserView { 5 | static func mock(user: User?) -> MyPageUserView { 6 | let view = MyPageUserView.loadFromNib() 7 | view.render(with: .init(user: user)) 8 | return view 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Components/MyPageViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Entity 4 | import ReactiveSwift 5 | 6 | extension MyPageViewController { 7 | static func mock(user: User?) -> MyPageViewController { 8 | MyPageViewController( 9 | presenter: MyPagePresenterMock(user: user), 10 | useCase: MyPageUseCaseMock(), 11 | router: MyPageRouterMock(), 12 | notification: UserStateNotificationMock() 13 | ) 14 | } 15 | } 16 | 17 | final class MyPagePresenterMock: MyPagePresenterProtocol { 18 | let viewModel: Property 19 | let command: Signal = .empty 20 | 21 | init(user: User?) { 22 | viewModel = .init(value: .init(state: .init(user: user))) 23 | } 24 | } 25 | 26 | final class MyPageUseCaseMock: MyPageUseCase { 27 | func refresh() {} 28 | func logout() {} 29 | } 30 | 31 | final class MyPageRouterMock: MyPageRouting { 32 | func presentLogin() {} 33 | func pushUserEdit() {} 34 | func showHome(resetRequired: Bool) {} 35 | } 36 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Components/UserEditViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import ReactiveSwift 4 | 5 | extension UserEditViewController { 6 | static func mock() -> UserEditViewController { 7 | UserEditViewController( 8 | presenter: UserEditPresenterMock(), 9 | useCase: UserEditUseCaseMock(), 10 | router: UserEditRouterMock(), 11 | notification: UserStateNotificationMock() 12 | ) 13 | } 14 | } 15 | 16 | final class UserEditPresenterMock: UserEditPresenterProtocol { 17 | let viewModel: Property = .init(value: .init()) 18 | let command: Signal = .empty 19 | } 20 | 21 | final class UserEditUseCaseMock: UserEditUseCase { 22 | func edit(userName: String, password: String) {} 23 | } 24 | 25 | final class UserEditRouterMock: UserEditRouting { 26 | func pop() {} 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Mocks/Foundations/UserStateNotificationMock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import ReactiveSwift 3 | 4 | final class UserStateNotificationMock: UserStateNotification { 5 | let userStateChanged: Signal = .empty 6 | 7 | func postUserStateChanged() {} 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Resources/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 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Scenarios/AllScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | 3 | struct AllScenarios: ScenarioProvider { 4 | static func addScenarios(into playbook: Playbook) { 5 | playbook 6 | .add(EntranceScenarios.self) 7 | .add(HomeScenarios.self) 8 | .add(LoginScenarios.self) 9 | .add(MyPageScenarios.self) 10 | .add(UserEditScenarios.self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Scenarios/EntranceScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct EntranceScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "Entrance") { 8 | Scenario("EntranceViewController", layout: .sizing(h: .fill, v: 1500)) { 9 | EntranceViewController.mock() 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Scenarios/HomeScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct HomeScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "Home") { 8 | Scenario("HomeHeaderView some User", layout: .fillH) { 9 | HomeHeaderView.mock(user: .init(id: "", name: "User Name", password: "password")) 10 | } 11 | 12 | Scenario("HomeHeaderView empty User", layout: .fillH) { 13 | HomeHeaderView.mock(user: nil) 14 | } 15 | 16 | Scenario("HomeViewController some User", layout: .sizing(h: .fill, v: 1500)) { 17 | HomeViewController.mock(user: .init(id: "", name: "User Name", password: "password")) 18 | } 19 | 20 | Scenario("HomeViewController empty User", layout: .sizing(h: .fill, v: 1500)) { 21 | HomeViewController.mock(user: nil) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Scenarios/LoginScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct LoginScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "Login") { 8 | Scenario("LoginViewController", layout: .sizing(h: .fill, v: 1500)) { 9 | LoginViewController.mock() 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Scenarios/MyPageScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct MyPageScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "MyPage") { 8 | Scenario("MyPageLabelView", layout: .fillH) { 9 | MyPageLabelView.mock(text: "Scenario Test") 10 | } 11 | 12 | Scenario("MyPageUserView some User", layout: .fillH) { 13 | MyPageUserView.mock(user: .init(id: "", name: "User Name", password: "password")) 14 | } 15 | 16 | Scenario("MyPageUserView empty User", layout: .fillH) { 17 | MyPageUserView.mock(user: nil) 18 | } 19 | 20 | Scenario("MyPageViewController some User", layout: .sizing(h: .fill, v: 1500)) { 21 | MyPageViewController.mock(user: .init(id: "", name: "User Name", password: "password")) 22 | } 23 | 24 | Scenario("MyPageViewController empty User", layout: .sizing(h: .fill, v: 1500)) { 25 | MyPageViewController.mock(user: nil) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/Scenarios/UserEditScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct UserEditScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "UserEdit") { 8 | Scenario("UserEditViewController", layout: .sizing(h: .fill, v: 1500)) { 9 | UserEditViewController.mock() 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Catalog/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import PlaybookUI 2 | import SwiftUI 3 | 4 | struct PlaybookView: View { 5 | enum Tab { 6 | case catalog 7 | case gallery 8 | } 9 | 10 | @State 11 | var tab = Tab.gallery 12 | 13 | var body: some View { 14 | TabView(selection: $tab) { 15 | PlaybookGallery() 16 | .tag(Tab.gallery) 17 | .tabItem { 18 | Image(systemName: "rectangle.grid.3x2") 19 | Text("Gallery") 20 | } 21 | 22 | PlaybookCatalog() 23 | .tag(Tab.catalog) 24 | .tabItem { 25 | Image(systemName: "doc.text.magnifyingglass") 26 | Text("Catalog") 27 | } 28 | } 29 | } 30 | } 31 | 32 | final class SceneDelegate: UIResponder, UIWindowSceneDelegate { 33 | var window: UIWindow? 34 | 35 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 36 | guard let windowScene = scene as? UIWindowScene else { return } 37 | Playbook.default.add(AllScenarios.self) 38 | 39 | let window = UIWindow(windowScene: windowScene) 40 | window.rootViewController = UIHostingController(rootView: PlaybookView()) 41 | 42 | self.window = window 43 | window.makeKeyAndVisible() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/CatalogSnapshot/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/CatalogSnapshot/SnapshotTests.swift: -------------------------------------------------------------------------------- 1 | import PlaybookSnapshot 2 | import XCTest 3 | 4 | final class SnapshotTests: XCTestCase { 5 | func testTakeSnapshot() throws { 6 | guard let directory = ProcessInfo.processInfo.environment["SNAPSHOT_DIR"] else { 7 | fatalError("Set directory to the build environment variables with key `SNAPSHOT_DIR`.") 8 | } 9 | 10 | try Playbook.default.run( 11 | Snapshot( 12 | directory: URL(fileURLWithPath: directory), 13 | clean: true, 14 | format: .png, 15 | scale: 1, 16 | keyWindow: UIApplication.shared.windows.first { $0.isKeyWindow }, 17 | devices: [ 18 | .iPhone11Pro(.portrait), 19 | .iPhoneSE(.portrait) 20 | ] 21 | ) 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/Home/HomeComposer.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import Entity 3 | 4 | public struct HomeComposer { 5 | enum SectionID { 6 | case contents 7 | } 8 | 9 | public struct State { 10 | public var user: User? 11 | 12 | public init( 13 | user: User? = nil 14 | ) { 15 | self.user = user 16 | } 17 | } 18 | 19 | public init() {} 20 | 21 | public func compose(state: State) -> Group
{ 22 | Group { 23 | Section( 24 | id: SectionID.contents, 25 | cells: { 26 | HomeHeaderComponent(user: state.user) 27 | }) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/Home/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Carbon 3 | import Entity 4 | import ReactiveSwift 5 | import UIKit 6 | 7 | public protocol HomeRouting: AnyObject {} 8 | 9 | public protocol HomePresenterProtocol { 10 | var dataState: Property { get } 11 | } 12 | 13 | public final class HomeViewController: UIViewController, Renderable { 14 | private let presenter: HomePresenterProtocol 15 | private let useCase: HomeUseCase 16 | private let router: HomeRouting 17 | private let notification: UserStateNotification 18 | 19 | private let renderer = Renderer( 20 | adapter: UITableViewAdapter(), 21 | updater: UITableViewUpdater() 22 | ) 23 | 24 | private var composer: HomeComposer { 25 | HomeComposer() 26 | } 27 | 28 | @IBOutlet private weak var tableView: UITableView! { 29 | didSet { 30 | tableView.allowsSelection = false 31 | tableView.delaysContentTouches = false 32 | tableView.separatorStyle = .none 33 | } 34 | } 35 | 36 | public init( 37 | presenter: HomePresenterProtocol, 38 | useCase: HomeUseCase, 39 | router: HomeRouting, 40 | notification: UserStateNotification 41 | ) { 42 | self.presenter = presenter 43 | self.useCase = useCase 44 | self.router = router 45 | self.notification = notification 46 | super.init(nibName: nil, bundle: .component) 47 | } 48 | 49 | @available(*, unavailable) 50 | required init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | public override func viewDidLoad() { 55 | super.viewDidLoad() 56 | 57 | renderer.target = tableView 58 | 59 | useCase.refresh <~ reactive.viewWillAppear.take(first: 1) 60 | .merge(with: notification.userStateChanged) 61 | 62 | reactive.render <~ presenter.dataState 63 | } 64 | 65 | public func render(with dataState: HomeComposer.State) { 66 | renderer.render { 67 | composer.compose(state: dataState) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/Home/HomeViewModel.swift: -------------------------------------------------------------------------------- 1 | public struct HomeViewModel { 2 | public var state: HomeComposer.State 3 | 4 | public init( 5 | state: HomeComposer.State = .init() 6 | ) { 7 | self.state = state 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/Home/Views/HomeHeaderView.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import Entity 3 | import UIKit 4 | 5 | public struct HomeHeaderComponent: IdentifiableComponent { 6 | public var user: User? 7 | 8 | public var id: String? { 9 | user?.id 10 | } 11 | 12 | public init(user: User?) { 13 | self.user = user 14 | } 15 | 16 | public func referenceSize(in bounds: CGRect) -> CGSize? { 17 | CGSize(width: bounds.width, height: 166) 18 | } 19 | 20 | public func shouldRender(next: HomeHeaderComponent, in content: HomeHeaderView) -> Bool { 21 | user?.name != next.user?.name 22 | } 23 | } 24 | 25 | public final class HomeHeaderView: UIView, NibLoadable, Renderable { 26 | @IBOutlet private weak var textLabel: UILabel! { 27 | didSet { 28 | textLabel.font = .systemFont(ofSize: 32) 29 | textLabel.textColor = .darkGray 30 | } 31 | } 32 | 33 | public func render(with component: HomeHeaderComponent) { 34 | textLabel.text = "Hello! " + (component.user?.name ?? "Guest") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/Login/LoginViewModel.swift: -------------------------------------------------------------------------------- 1 | public struct LoginViewModel { 2 | public enum Command { 3 | case loginCompleted 4 | } 5 | 6 | public var loginProgress: LoadProgress 7 | 8 | public init( 9 | loginProgress: LoadProgress = .default 10 | ) { 11 | self.loginProgress = loginProgress 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/MyPage/Entities/MyPageItem.swift: -------------------------------------------------------------------------------- 1 | public enum MyPageItem { 2 | case userEdit 3 | case login 4 | case logout 5 | 6 | public var text: String { 7 | switch self { 8 | case .userEdit: 9 | return "User Edit" 10 | 11 | case .login: 12 | return "Login" 13 | 14 | case .logout: 15 | return "Logout" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/MyPage/MyPageComposer.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import Entity 3 | 4 | public struct MyPageComposer { 5 | enum SectionID { 6 | case contents 7 | } 8 | 9 | public enum Event { 10 | case selectItem(MyPageItem) 11 | } 12 | 13 | public struct State { 14 | public var user: User? 15 | 16 | public init( 17 | user: User? = nil 18 | ) { 19 | self.user = user 20 | } 21 | } 22 | 23 | public var event: (Event) -> Void 24 | 25 | public init( 26 | event: @escaping (Event) -> Void 27 | ) { 28 | self.event = event 29 | } 30 | 31 | public func compose(state: State) -> Group
{ 32 | Group { 33 | Section( 34 | id: SectionID.contents, 35 | cells: { 36 | MyPageUserComponent(user: state.user) 37 | 38 | if state.user.isNone { 39 | MyPageLabelComponent(text: MyPageItem.login.text) { 40 | self.event(.selectItem(.login)) 41 | } 42 | } 43 | else { 44 | MyPageLabelComponent(text: MyPageItem.userEdit.text) { 45 | self.event(.selectItem(.userEdit)) 46 | } 47 | 48 | MyPageLabelComponent(text: MyPageItem.logout.text) { 49 | self.event(.selectItem(.logout)) 50 | } 51 | } 52 | }) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/MyPage/MyPageViewModel.swift: -------------------------------------------------------------------------------- 1 | public struct MyPageViewModel { 2 | public enum Command { 3 | case logoutCompleted 4 | } 5 | 6 | public var state: MyPageComposer.State 7 | 8 | public init( 9 | state: MyPageComposer.State = .init() 10 | ) { 11 | self.state = state 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/MyPage/Views/MyPageLabelView.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import UIKit 3 | 4 | public struct MyPageLabelComponent: IdentifiableComponent { 5 | public var text: String 6 | public var tap: () -> Void 7 | 8 | public var id: String { 9 | text 10 | } 11 | 12 | public init( 13 | text: String, 14 | tap: @escaping () -> Void 15 | ) { 16 | self.text = text 17 | self.tap = tap 18 | } 19 | 20 | public func referenceSize(in bounds: CGRect) -> CGSize? { 21 | CGSize(width: bounds.width, height: 60) 22 | } 23 | 24 | public func shouldRender(next: MyPageLabelComponent, in content: MyPageLabelView) -> Bool { 25 | text != next.text 26 | } 27 | } 28 | 29 | public final class MyPageLabelView: UIControl, NibLoadable, Renderable { 30 | @IBOutlet private weak var label: UILabel! { 31 | didSet { 32 | label.textColor = .darkGray 33 | label.font = .systemFont(ofSize: 24) 34 | } 35 | } 36 | 37 | public var tap: (() -> Void)? 38 | 39 | public override func awakeFromNib() { 40 | super.awakeFromNib() 41 | 42 | reactive.tap 43 | .take(duringLifetimeOf: self) 44 | .observeValues { [weak self] state in 45 | self?.tap?() 46 | } 47 | } 48 | 49 | public func render(with component: MyPageLabelComponent) { 50 | label.text = component.text 51 | tap = component.tap 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/MyPage/Views/MyPageUserView.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import Entity 3 | import UIKit 4 | 5 | public struct MyPageUserComponent: IdentifiableComponent { 6 | public var user: User? 7 | 8 | public var id: String? { 9 | user?.id 10 | } 11 | 12 | public init( 13 | user: User? 14 | ) { 15 | self.user = user 16 | } 17 | 18 | public func referenceSize(in bounds: CGRect) -> CGSize? { 19 | CGSize(width: bounds.width, height: 252) 20 | } 21 | 22 | public func shouldRender(next: MyPageUserComponent, in content: MyPageUserView) -> Bool { 23 | user?.name != next.user?.name 24 | } 25 | } 26 | 27 | public final class MyPageUserView: UIView, NibLoadable, Renderable { 28 | @IBOutlet private weak var imageView: UIImageView! { 29 | didSet { 30 | imageView.contentMode = .center 31 | imageView.tintColor = .white 32 | imageView.image = UIImage(named: "User", in: .component, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) 33 | imageView.backgroundColor = .lightGray 34 | imageView.layer.cornerRadius = imageView.bounds.height / 2 35 | } 36 | } 37 | 38 | @IBOutlet private weak var userNameLabel: UILabel! { 39 | didSet { 40 | userNameLabel.textAlignment = .center 41 | userNameLabel.font = .boldSystemFont(ofSize: 32) 42 | userNameLabel.textColor = .darkGray 43 | } 44 | } 45 | 46 | public func render(with component: MyPageUserComponent) { 47 | userNameLabel.text = component.user?.name ?? "Guest" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/NavigationController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol NavigationRouting: AnyObject { 4 | 5 | } 6 | 7 | public final class NavigationController: UINavigationController { 8 | public let router: NavigationRouting 9 | 10 | public init(router: NavigationRouting) { 11 | self.router = router 12 | super.init(nibName: nil, bundle: .component) 13 | } 14 | 15 | @available(*, unavailable) 16 | required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/RootViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol RootRouting: AnyObject { 4 | func showTabPages() 5 | func showEntrance() 6 | } 7 | 8 | public final class RootViewController: UIViewController { 9 | private let router: RootRouting 10 | 11 | public init(router: RootRouting) { 12 | self.router = router 13 | super.init(nibName: nil, bundle: .component) 14 | } 15 | 16 | @available(*, unavailable) 17 | required init?(coder aDecoder: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | 21 | public override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | reactive.viewDidAppear 25 | .take(first: 1) 26 | .take(duringLifetimeOf: self) 27 | .observeValues { [weak self] in 28 | self?.router.showEntrance() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/RootViewController.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 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/TabBarController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol TabRouting: AnyObject { 4 | 5 | } 6 | 7 | public final class TabBarController: UITabBarController { 8 | public let router: TabRouting 9 | 10 | public init(router: TabRouting) { 11 | self.router = router 12 | super.init(nibName: nil, bundle: .component) 13 | } 14 | 15 | @available(*, unavailable) 16 | required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | public override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Components/User/Edit/UserEditViewModel.swift: -------------------------------------------------------------------------------- 1 | public struct UserEditViewModel { 2 | public enum Command { 3 | case editCompleted 4 | } 5 | 6 | public var loginProgress: LoadProgress 7 | 8 | public init( 9 | loginProgress: LoadProgress = .default 10 | ) { 11 | self.loginProgress = loginProgress 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Extensions/Bundle+Token.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bundle { 4 | private final class Token {} 5 | 6 | static var component: Bundle { 7 | Bundle(for: Token.self) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Extensions/Optional+Utility.swift: -------------------------------------------------------------------------------- 1 | public extension Optional { 2 | var isSome: Bool { 3 | guard case .some = self else { return false } 4 | return true 5 | } 6 | 7 | var isNone: Bool { 8 | self == nil 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Extensions/Reactive/Refreshable+Reactive.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import ReactiveSwift 3 | 4 | public extension Refreshable { 5 | var refresh: BindingTarget { 6 | reactive.makeBindingTarget { base, _ in 7 | base.refresh() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Extensions/Reactive/UIControl+Reactive.swift: -------------------------------------------------------------------------------- 1 | import ReactiveCocoa 2 | import ReactiveSwift 3 | import UIKit 4 | 5 | extension Reactive where Base: UIControl { 6 | var tap: Signal { 7 | controlEvents(.touchUpInside).map(value: ()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Extensions/UIKit/UITableView+Utility.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UITableView { 4 | override open func touchesShouldCancel(in view: UIView) -> Bool { 5 | !delaysContentTouches 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Extensions/UIKit/UIViewController+Utility.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UIViewController { 4 | var isPresented: Bool { 5 | presentingViewController.isSome 6 | } 7 | 8 | func add(childController controller: UIViewController, to view: UIView? = nil) { 9 | addChild(controller) 10 | let parentView: UIView = view ?? self.view 11 | controller.view.frame = parentView.bounds 12 | parentView.addSubview(controller.view) 13 | controller.didMove(toParent: self) 14 | } 15 | 16 | func removeFromParentController() { 17 | willMove(toParent: nil) 18 | view.removeFromSuperview() 19 | removeFromParent() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Foundations/LoadProgress.swift: -------------------------------------------------------------------------------- 1 | public enum LoadProgress: Equatable { 2 | case `default` 3 | case loading 4 | case loadSucceeded 5 | case loadFailed 6 | 7 | public var isDefault: Bool { 8 | switch self { 9 | case .default: 10 | return true 11 | 12 | case .loading, 13 | .loadSucceeded, 14 | .loadFailed: 15 | return false 16 | } 17 | } 18 | 19 | public var isLoading: Bool { 20 | switch self { 21 | case .loading: 22 | return true 23 | 24 | case .default, 25 | .loadSucceeded, 26 | .loadFailed: 27 | return false 28 | } 29 | } 30 | 31 | public var isLoaded: Bool { 32 | switch self { 33 | case .loadSucceeded, .loadFailed: 34 | return true 35 | 36 | case .default, .loading: 37 | return false 38 | } 39 | } 40 | 41 | public var isLoadSucceeded: Bool { 42 | switch self { 43 | case .loadSucceeded: 44 | return true 45 | 46 | case .default, .loading, .loadFailed: 47 | return false 48 | } 49 | } 50 | 51 | public var isLoadFailed: Bool { 52 | switch self { 53 | case .loadFailed: 54 | return true 55 | 56 | case .default, .loading, .loadSucceeded: 57 | return false 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Foundations/NibBundler.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol NibBundler: AnyObject { 4 | static var nib: UINib { get } 5 | static var nibName: String { get } 6 | static var nibBundle: Bundle? { get } 7 | } 8 | 9 | public extension NibBundler { 10 | static var nib: UINib { 11 | UINib(nibName: nibName, bundle: nibBundle) 12 | } 13 | 14 | static var nibName: String { 15 | String(describing: self) 16 | } 17 | 18 | static var nibBundle: Bundle? { 19 | Bundle(for: self) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Foundations/NibLoadable.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import UIKit 3 | 4 | public protocol NibLoadable: NibBundler {} 5 | 6 | public extension NibLoadable where Self: UIView { 7 | static func loadFromNib() -> Self { 8 | guard let view = nib.instantiate(withOwner: nil, options: nil).first as? Self else { 9 | fatalError("The nib \(nib) expected its root view to be of type \(self)") 10 | } 11 | return view 12 | } 13 | } 14 | 15 | public extension Component where Content: UIView & NibLoadable { 16 | func renderContent() -> Content { 17 | .loadFromNib() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Foundations/Notification/UserStateNotification.swift: -------------------------------------------------------------------------------- 1 | import ReactiveSwift 2 | 3 | public protocol UserStateNotification: AnyObject { 4 | var userStateChanged: Signal { get } 5 | 6 | func postUserStateChanged() 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Foundations/Renderable.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import ReactiveSwift 3 | import UIKit 4 | 5 | public protocol Renderable: AnyObject { 6 | associatedtype Component 7 | 8 | func render(with component: Component) 9 | } 10 | 11 | extension Reactive where Base: Renderable { 12 | var render: BindingTarget { 13 | makeBindingTarget { base, component in 14 | base.render(with: component) 15 | } 16 | } 17 | } 18 | 19 | public extension Component where Content: Renderable, Content.Component == Self { 20 | func render(in content: Content) { 21 | content.render(with: self) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Loading/LoadingViewController.swift: -------------------------------------------------------------------------------- 1 | import ReactiveSwift 2 | import UIKit 3 | 4 | public extension Reactive where Base: LoadingViewController { 5 | func presented(from controller: UIViewController) -> BindingTarget { 6 | makeBindingTarget { [weak controller] base, isPresented in 7 | guard let controller = controller, base.isPresented != isPresented else { return } 8 | 9 | isPresented 10 | ? controller.present(base, animated: false) 11 | : base.dismiss(animated: false) 12 | } 13 | } 14 | } 15 | 16 | public final class LoadingViewController: UIViewController { 17 | @IBOutlet private weak var indicator: UIActivityIndicatorView! 18 | 19 | public required init() { 20 | super.init(nibName: nil, bundle: .component) 21 | 22 | modalPresentationStyle = .overFullScreen 23 | } 24 | 25 | @available(*, unavailable) 26 | public required init?(coder aDecoder: NSCoder) { 27 | fatalError("init(coder:) has not been implemented") 28 | } 29 | 30 | public override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | view.backgroundColor = UIColor.black.withAlphaComponent(0.2) 34 | } 35 | 36 | 37 | override public func viewWillAppear(_ animated: Bool) { 38 | super.viewWillAppear(animated) 39 | indicator.startAnimating() 40 | } 41 | 42 | override public func viewDidDisappear(_ animated: Bool) { 43 | super.viewDidDisappear(animated) 44 | indicator.stopAnimating() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Resources/Assets.xcassets/User.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "User.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Resources/Assets.xcassets/User.imageset/User.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyoheiG3/Plan/49f53128eac924d9723b31ee4b0240d62bd0e12d/Examples/Example-iOS-RAC/Component/Resources/Assets.xcassets/User.imageset/User.pdf -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Component/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Configs/Base.xcconfig: -------------------------------------------------------------------------------- 1 | IPHONEOS_DEPLOYMENT_TARGET = 11.0 2 | SWIFT_VERSION = 5.0 3 | ARCHS = $ARCHS_STANDARD_64_BIT 4 | VALID_ARCHS = $ARCHS_STANDARD_64_BIT 5 | PROVISIONING_PROFILE_SPECIFIER = 6 | DEVELOPMENT_TEAM = 7 | CODE_SIGN_IDENTITY = 8 | CODE_SIGN_ENTITLEMENTS = 9 | CODE_SIGN_STYLE = Manual 10 | DEFINES_MODULE = NO 11 | PRODUCT_BUNDLE_IDENTIFIER = com.kyoheiito.Plan.Example-iOS-RAC.$(TARGET_NAME) 12 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 13 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Configs/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | //:configuration = Dev 3 | 4 | OTHER_SWIFT_FLAGS = $(inherited) -DDEBUG 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Configs/Prd.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | //:configuration = Prd 3 | 4 | OTHER_SWIFT_FLAGS = $(inherited) -DRELEASE 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Configs/Stg.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | //:configuration = Stg 3 | 4 | OTHER_SWIFT_FLAGS = $(inherited) -DSTAGE 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Configs/Test.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | //:configuration = Test 3 | 4 | OTHER_SWIFT_FLAGS = $(inherited) -DTEST 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Entity/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Entity/User.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct User: Codable { 4 | public var id: String 5 | public var name: String 6 | public var password: String 7 | 8 | public init( 9 | id: String, 10 | name: String, 11 | password: String 12 | ) { 13 | self.id = id 14 | self.name = name 15 | self.password = password 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/EntityTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | final class AppDelegate: UIResponder, UIApplicationDelegate { 5 | lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds) 6 | lazy var router = AppRouter(container: window) 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | router.start() 10 | 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Base.xcconfig: -------------------------------------------------------------------------------- 1 | TARGETED_DEVICE_FAMILY = 1 2 | SDKROOT = iphoneos 3 | SUPPORTED_PLATFORMS = iphonesimulator iphoneos 4 | DEVELOPMENT_LANGUAGE = Japan 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Environment/Dev.xcconfig" 3 | #include "./Provisioning/Development.xcconfig" 4 | //:configuration = Dev 5 | 6 | OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -warn-long-function-bodies=500 -Xfrontend -warn-long-expression-type-checking=200 7 | PRODUCT_DISPLAY_NAME = Clean-Dev 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Environment/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | APS_ENVIRONMENT = development 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Environment/Prd.xcconfig: -------------------------------------------------------------------------------- 1 | APS_ENVIRONMENT = production 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Environment/Stg.xcconfig: -------------------------------------------------------------------------------- 1 | APS_ENVIRONMENT = development 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Prd.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Environment/Prd.xcconfig" 3 | #include "./Provisioning/Distribution.xcconfig" 4 | //:configuration = Prd 5 | 6 | PRODUCT_DISPLAY_NAME = Clean 7 | CLANG_ENABLE_CODE_COVERAGE = NO 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Provisioning/Development.xcconfig: -------------------------------------------------------------------------------- 1 | PRODUCT_BUNDLE_IDENTIFIER = com.kyoheiito.Plan.Example-iOS-RAC 2 | CODE_SIGN_IDENTITY = iPhone Developer 3 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Provisioning/Distribution.xcconfig: -------------------------------------------------------------------------------- 1 | PRODUCT_BUNDLE_IDENTIFIER = com.kyoheiito.Plan.Example-iOS-RAC 2 | CODE_SIGN_IDENTITY = iPhone Distribution 3 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Stg.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Environment/Stg.xcconfig" 3 | #include "./Provisioning/Development.xcconfig" 4 | //:configuration = Stg 5 | 6 | OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -warn-long-function-bodies=500 -Xfrontend -warn-long-expression-type-checking=200 7 | PRODUCT_DISPLAY_NAME = Clean-Stg 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Configs/Test.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Environment/Dev.xcconfig" 3 | #include "./Provisioning/Development.xcconfig" 4 | //:configuration = Test 5 | 6 | PRODUCT_DISPLAY_NAME = Clean-Test 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Extensions/Bundle+Info.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bundle { 4 | struct Info { 5 | var key: String 6 | 7 | init(key: String) { 8 | self.key = key 9 | } 10 | 11 | func object(for key: String) -> T? { 12 | Bundle.main.object(forInfoDictionaryKey: key) as? T 13 | } 14 | 15 | func toString() -> String { 16 | guard let string: String = object(for: key) else { 17 | fatalError(#""\#(key)" is not existing"#) 18 | } 19 | return string 20 | } 21 | } 22 | } 23 | 24 | extension Bundle.Info { 25 | static var id: String { 26 | Bundle.main.bundleIdentifier ?? Bundle.Info(key: "CFBundleIdentifier").toString() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Extensions/Gateway/Repositories/UserRepository.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import Foundation 3 | import ReactiveSwift 4 | 5 | extension UserRepository.Dependency { 6 | static let `default` = Self( 7 | scheduler: QueueScheduler.main, 8 | userDefaults: UserDefaults.standard, 9 | uuidGen: UUID() 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Extensions/Notification/NotificationCenter+UserState.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import Foundation 3 | import ReactiveSwift 4 | 5 | extension NotificationCenter: UserStateNotification { 6 | public var userStateChanged: Signal { 7 | reactive.notifications(forName: .userStateChanged) 8 | .map(value: ()) 9 | } 10 | 11 | public func postUserStateChanged() { 12 | post(name: .userStateChanged, object: nil) 13 | } 14 | } 15 | 16 | private extension Notification.Name { 17 | static let userStateChanged = Notification.Name(Bundle.Info.id + ".UserStateChanged") 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Extensions/UIKit/UITabBarController+Utility.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UITabBarController { 4 | var selectedBarItemTag: Int? { 5 | get { 6 | selectedViewController?.tabBarItem.tag 7 | } 8 | set { 9 | selectedViewController = viewControllers?.first { controller in 10 | controller.tabBarItem.tag == newValue 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Extensions/UseCase/Interactors/HomeInteractor.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import UseCase 3 | 4 | extension HomeInteractor.Dependency { 5 | static var `default` = Self( 6 | userRepository: UserRepository(dependency: .default) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Extensions/UseCase/Interactors/LoginInteractor.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import UseCase 3 | 4 | extension LoginInteractor.Dependency { 5 | static var `default` = Self( 6 | userRepository: UserRepository(dependency: .default) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Extensions/UseCase/Interactors/MyPageInteractor.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import UseCase 3 | 4 | extension MyPageInteractor.Dependency { 5 | static var `default` = Self( 6 | userRepository: UserRepository(dependency: .default) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Extensions/UseCase/Interactors/UserEditInteractor.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import UseCase 3 | 4 | extension UserEditInteractor.Dependency { 5 | static var `default` = Self( 6 | userRepository: UserRepository(dependency: .default) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Resources/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 | 27 | 28 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Router/AppRouter.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import UIKit 3 | 4 | final class AppRouter: RootRouting { 5 | private(set) weak var container: UIWindow? 6 | private var rootRouter: RootRouter? 7 | 8 | init(container: UIWindow?) { 9 | self.container = container 10 | } 11 | 12 | func start() { 13 | let controller = RootViewController(router: self) 14 | rootRouter = RootRouter(container: controller) 15 | container?.rootViewController = controller 16 | container?.makeKeyAndVisible() 17 | } 18 | 19 | func showTabPages() { 20 | rootRouter?.showTabPages(initialTab: .home) 21 | } 22 | 23 | func showEntrance() { 24 | rootRouter?.showEntrance() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Router/NavigationRouter.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import UseCase 3 | import Foundation 4 | import Presentation 5 | 6 | final class NavigationRouter: NavigationRouting, MyPageRouting, HomeRouting, LoginRouting, UserEditRouting { 7 | private weak var container: NavigationController? 8 | private weak var tabRouter: TabRouter? 9 | 10 | init(container: NavigationController?, tabRouter: TabRouter?) { 11 | self.container = container 12 | self.tabRouter = tabRouter 13 | } 14 | 15 | func pushUserEdit() { 16 | let presenter = UserEditPresenter(store: .init(), translator: .init()) 17 | let interactor = UserEditInteractor(dispatcher: presenter.asDispatcher(), dependency: .default) 18 | let controller = UserEditViewController(presenter: presenter, useCase: interactor, router: self, notification: NotificationCenter.default) 19 | container?.pushViewController(controller, animated: true) 20 | } 21 | 22 | func presentLogin() { 23 | let navigation = NavigationController(router: self) 24 | let router = NavigationRouter(container: navigation, tabRouter: tabRouter) 25 | let presenter = LoginPresenter(store: .init(), translator: .init()) 26 | let interactor = LoginInteractor(dispatcher: presenter.asDispatcher(), dependency: .default) 27 | let controller = LoginViewController(presenter: presenter, useCase: interactor, router: router, notification: NotificationCenter.default) 28 | navigation.viewControllers = [controller] 29 | container?.present(navigation, animated: true) 30 | } 31 | 32 | func showHome(resetRequired: Bool = true) { 33 | tabRouter?.showHome(resetRequired: resetRequired) 34 | } 35 | 36 | func dismiss() { 37 | container?.dismiss(animated: true) 38 | } 39 | 40 | func pop() { 41 | container?.popViewController(animated: true) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Example-iOS-RAC/Router/RootRouter.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import UIKit 3 | 4 | final class RootRouter: TabRouting, NavigationRouting, EntranceRouting { 5 | private(set) weak var container: RootViewController? 6 | private lazy var tabContainer = TabBarController(router: self) 7 | private lazy var tabRouter = TabRouter(container: tabContainer, rootRouter: self) 8 | 9 | private var currentController: UIViewController? { 10 | didSet { 11 | oldValue?.removeFromParentController() 12 | guard let newValue = currentController else { 13 | return 14 | } 15 | 16 | container?.add(childController: newValue) 17 | } 18 | } 19 | 20 | init(container: RootViewController?) { 21 | self.container = container 22 | } 23 | 24 | func showTabPages(initialTab: RootTab) { 25 | tabRouter.resetTabPages() 26 | tabRouter.select(tab: initialTab) 27 | currentController = tabRouter.container 28 | } 29 | 30 | func showEntrance() { 31 | let controller = EntranceViewController(router: self) 32 | container?.present(controller, animated: false) 33 | } 34 | 35 | func showHome() { 36 | showTabPages(initialTab: .home) 37 | container?.dismiss(animated: true) 38 | } 39 | 40 | func showMyPage() { 41 | showTabPages(initialTab: .myPage) 42 | container?.dismiss(animated: true) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Gateway/Foundations/DefaultsKey.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct DefaultsKey { 4 | fileprivate var key: String 5 | 6 | static let token = DefaultsKey(key: "token") 7 | static let user = DefaultsKey(key: "user") 8 | } 9 | 10 | extension UserDefaults { 11 | func set(_ value: Any?, forKey key: DefaultsKey) { 12 | self.set(value, forKey: key.key) 13 | } 14 | 15 | func data(forKey key: DefaultsKey) -> Data? { 16 | self.data(forKey: key.key) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Gateway/Foundations/UUIDGen.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol UUIDGen { 4 | var uuidString: String { get } 5 | } 6 | 7 | extension UUID: UUIDGen {} 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Gateway/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Gateway/Repositories/UserRepository.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | import Foundation 3 | import ReactiveSwift 4 | import UseCase 5 | 6 | public final class UserRepository: UserRepositoryProtocol { 7 | public struct Dependency { 8 | public var scheduler: DateScheduler 9 | public var userDefaults: UserDefaults 10 | public var uuidGen: UUIDGen 11 | 12 | public init( 13 | scheduler: DateScheduler, 14 | userDefaults: UserDefaults, 15 | uuidGen: UUIDGen 16 | ) { 17 | self.scheduler = scheduler 18 | self.userDefaults = userDefaults 19 | self.uuidGen = uuidGen 20 | } 21 | } 22 | 23 | private let dependency: Dependency 24 | 25 | public init(dependency: Dependency) { 26 | self.dependency = dependency 27 | } 28 | 29 | public func loginUser() -> User? { 30 | try? dependency.userDefaults.data(forKey: .user).map { 31 | try JSONDecoder().decode(User.self, from: $0) 32 | } 33 | } 34 | 35 | public func login(userName: String, password: String) -> SignalProducer { 36 | let dependency = self.dependency 37 | return SignalProducer { observer, lifetime in 38 | let user = User( 39 | id: dependency.uuidGen.uuidString, 40 | name: userName, 41 | password: password 42 | ) 43 | 44 | dependency.userDefaults.set(user.id, forKey: .token) 45 | 46 | let data = try? JSONEncoder().encode(user) 47 | dependency.userDefaults.set(data, forKey: .user) 48 | observer.send(value: user) 49 | observer.sendCompleted() 50 | } 51 | .delay(1, on: dependency.scheduler) 52 | } 53 | 54 | public func logout() { 55 | dependency.userDefaults.set(nil, forKey: .token) 56 | dependency.userDefaults.set(nil, forKey: .user) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/GatewayTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Makefile: -------------------------------------------------------------------------------- 1 | carthageModule := 2 | 3 | # XcodeGen 4 | 5 | .PHONY: run-xcodegen 6 | run-xcodegen: 7 | xcodegen 8 | 9 | # Carthage 10 | 11 | .PHONY: bootstrap-carthage 12 | bootstrap-carthage: 13 | carthage bootstrap --platform ios --use-ssh --no-use-binaries --cache-builds 14 | 15 | .PHONY: update-carthage 16 | update-carthage: 17 | carthage update --platform ios --use-ssh --no-use-binaries --cache-builds ${carthageModule} 18 | 19 | .PHONY: renew-carthage 20 | renew-carthage: 21 | carthage update --platform ios --use-ssh --no-use-binaries 22 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Presentation/Components/Home/HomePresenter.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Plan 4 | import ReactiveSwift 5 | 6 | public final class HomePresenter: Presenter, HomePresenterProtocol { 7 | public var dataState: Property { 8 | store.viewModel.map(\.state) 9 | } 10 | } 11 | 12 | public final class HomeStore: Store { 13 | fileprivate let viewModel = MutableProperty(HomeViewModel()) 14 | 15 | public init() {} 16 | } 17 | 18 | public struct HomeTranslator: Translator { 19 | public init() {} 20 | 21 | public func translate(action: HomeUseCaseAction, store: HomeStore) { 22 | switch action { 23 | case .updateUser(let user): 24 | store.viewModel.value.state.user = user 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Presentation/Components/Login/LoginPresenter.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Plan 4 | import ReactiveSwift 5 | 6 | public final class LoginPresenter: Presenter, LoginPresenterProtocol { 7 | public var viewModel: Property { 8 | Property(store.viewModel) 9 | } 10 | 11 | public var command: Signal { 12 | store.command.output 13 | } 14 | } 15 | 16 | public final class LoginStore: Store { 17 | fileprivate let viewModel = MutableProperty(LoginViewModel()) 18 | fileprivate let command = Signal.pipe() 19 | 20 | public init() {} 21 | } 22 | 23 | public struct LoginTranslator: Translator { 24 | public init() {} 25 | 26 | public func translate(action: LoginUseCaseAction, store: LoginStore) { 27 | switch action { 28 | case .loading: 29 | store.viewModel.value.loginProgress = .loading 30 | 31 | case .login(.success): 32 | store.viewModel.value.loginProgress = .loadSucceeded 33 | store.command.input.send(value: .loginCompleted) 34 | 35 | case .login(.failure(let error)): 36 | print("login failed \(error)") 37 | store.viewModel.value.loginProgress = .loadFailed 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Presentation/Components/MyPage/MyPagePresenter.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Plan 4 | import ReactiveSwift 5 | 6 | public final class MyPagePresenter: Presenter, MyPagePresenterProtocol { 7 | public var viewModel: Property { 8 | Property(store.viewModel) 9 | } 10 | 11 | public var command: Signal { 12 | store.command.output 13 | } 14 | } 15 | 16 | public final class MyPageStore: Store { 17 | fileprivate let viewModel = MutableProperty(MyPageViewModel()) 18 | fileprivate let command = Signal.pipe() 19 | 20 | public init() {} 21 | } 22 | 23 | public struct MyPageTranslator: Translator { 24 | public init() {} 25 | 26 | public func translate(action: MyPageUseCaseAction, store: MyPageStore) { 27 | switch action { 28 | case .updateUser(let user): 29 | store.viewModel.value.state.user = user 30 | 31 | case .logout: 32 | store.command.input.send(value: .logoutCompleted) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Presentation/Components/User/Edit/UserEditPresenter.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Plan 4 | import ReactiveSwift 5 | 6 | public final class UserEditPresenter: Presenter, UserEditPresenterProtocol { 7 | public var viewModel: Property { 8 | Property(store.viewModel) 9 | } 10 | 11 | public var command: Signal { 12 | store.command.output 13 | } 14 | } 15 | 16 | public final class UserEditStore: Store { 17 | fileprivate let viewModel = MutableProperty(UserEditViewModel()) 18 | fileprivate let command = Signal.pipe() 19 | 20 | public init() {} 21 | } 22 | 23 | public struct UserEditTranslator: Translator { 24 | public init() {} 25 | 26 | public func translate(action: UserEditUseCaseAction, store: UserEditStore) { 27 | switch action { 28 | case .loading: 29 | store.viewModel.value.loginProgress = .loading 30 | 31 | case .edit(.success): 32 | store.viewModel.value.loginProgress = .loadSucceeded 33 | store.command.input.send(value: .editCompleted) 34 | 35 | case .edit(.failure(let error)): 36 | print("edit failed \(error)") 37 | store.viewModel.value.loginProgress = .loadFailed 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Presentation/Extensions/Translator+Executor.swift: -------------------------------------------------------------------------------- 1 | import Plan 2 | 3 | extension Translator { 4 | public var executor: Executor { 5 | .main 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/Presentation/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/PresentationTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/README.md: -------------------------------------------------------------------------------- 1 | # Example-iOS-RAC 2 | 3 | [Read me](../README.md) about this Example. 4 | 5 | ## Requirements 6 | 7 | - Swift 5 8 | - iOS 11.0 or later 9 | 10 | ## Usage 11 | 12 | Install some tools to build the project. 13 | 14 | #### Carthage 15 | 16 | ```bash 17 | $ brew install carthage 18 | ``` 19 | 20 | #### XcodeGen 21 | 22 | ```bash 23 | $ brew install xcodegen 24 | ``` 25 | 26 | Execute the following commands. 27 | 28 | ```bash 29 | $ make bootstrap-carthage 30 | $ make run-xcodegen 31 | $ open Example-iOS-RAC.xcodeproj 32 | ``` 33 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/UseCase/Components/Home/HomeInteractor.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Plan 3 | import ReactiveSwift 4 | 5 | public final class HomeInteractor: Interactor, HomeUseCase { 6 | public struct Dependency { 7 | public var userRepository: UserRepositoryProtocol 8 | 9 | public init( 10 | userRepository: UserRepositoryProtocol 11 | ) { 12 | self.userRepository = userRepository 13 | } 14 | } 15 | 16 | private let dependency: Dependency 17 | 18 | public init(dispatcher: D, dependency: Dependency) where D.Action == Action { 19 | self.dependency = dependency 20 | super.init(dispatcher: dispatcher) 21 | } 22 | 23 | public func refresh() { 24 | dispatcher.dispatch(.updateUser(dependency.userRepository.loginUser())) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/UseCase/Components/Login/LoginInteractor.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Plan 3 | import ReactiveSwift 4 | 5 | public final class LoginInteractor: Interactor, LoginUseCase { 6 | public struct Dependency { 7 | public var userRepository: UserRepositoryProtocol 8 | 9 | public init( 10 | userRepository: UserRepositoryProtocol 11 | ) { 12 | self.userRepository = userRepository 13 | } 14 | } 15 | 16 | private let dependency: Dependency 17 | private let loginDisposable: ScopedDisposable = ScopedSerialDisposable() 18 | 19 | public init(dispatcher: D, dependency: Dependency) where D.Action == Action { 20 | self.dependency = dependency 21 | super.init(dispatcher: dispatcher) 22 | } 23 | 24 | public func login(userName: String, password: String) { 25 | dispatcher.dispatch(.loading) 26 | loginDisposable.serial = dependency.userRepository.login(userName: userName, password: password) 27 | .startWithResult { [weak self] result in 28 | self?.dispatcher.dispatch(.login(result)) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/UseCase/Components/MyPage/MyPageInteractor.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Plan 3 | import ReactiveSwift 4 | 5 | public final class MyPageInteractor: Interactor, MyPageUseCase { 6 | public struct Dependency { 7 | public var userRepository: UserRepositoryProtocol 8 | 9 | public init( 10 | userRepository: UserRepositoryProtocol 11 | ) { 12 | self.userRepository = userRepository 13 | } 14 | } 15 | 16 | private let dependency: Dependency 17 | 18 | public init(dispatcher: D, dependency: Dependency) where D.Action == Action { 19 | self.dependency = dependency 20 | super.init(dispatcher: dispatcher) 21 | } 22 | 23 | public func refresh() { 24 | dispatcher.dispatch(.updateUser(dependency.userRepository.loginUser())) 25 | } 26 | 27 | public func logout() { 28 | dependency.userRepository.logout() 29 | dispatcher.dispatch(.logout) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/UseCase/Components/User/Edit/UserEditInteractor.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Plan 3 | import ReactiveSwift 4 | 5 | public final class UserEditInteractor: Interactor, UserEditUseCase { 6 | public struct Dependency { 7 | public var userRepository: UserRepositoryProtocol 8 | 9 | public init( 10 | userRepository: UserRepositoryProtocol 11 | ) { 12 | self.userRepository = userRepository 13 | } 14 | } 15 | 16 | private let dependency: Dependency 17 | private let editDisposable: ScopedDisposable = ScopedSerialDisposable() 18 | 19 | public init(dispatcher: D, dependency: Dependency) where D.Action == Action { 20 | self.dependency = dependency 21 | super.init(dispatcher: dispatcher) 22 | } 23 | 24 | public func edit(userName: String, password: String) { 25 | dispatcher.dispatch(.loading) 26 | editDisposable.serial = dependency.userRepository.login(userName: userName, password: password) 27 | .startWithResult { [weak self] result in 28 | self?.dispatcher.dispatch(.edit(result)) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/UseCase/Foundations/ScopedSerialDisposable.swift: -------------------------------------------------------------------------------- 1 | import ReactiveSwift 2 | 3 | public typealias ScopedSerialDisposable = ScopedDisposable 4 | 5 | public extension ScopedDisposable where Inner: SerialDisposable { 6 | convenience init() { 7 | self.init(Inner()) 8 | } 9 | 10 | var serial: Disposable? { 11 | get { inner.inner } 12 | set { inner.inner = newValue } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/UseCase/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/UseCase/Repositories/UserRepository.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | import ReactiveSwift 3 | 4 | public protocol UserRepositoryProtocol { 5 | func loginUser() -> User? 6 | func login(userName: String, password: String) -> SignalProducer 7 | func logout() 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-RAC/UseCaseTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Boundary/Components/Home/HomeUseCase.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | 3 | public enum HomeUseCaseAction { 4 | case updateUser(User?) 5 | } 6 | 7 | public protocol HomeUseCase: Refreshable {} 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Boundary/Components/Login/LoginUseCase.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | 3 | public enum LoginUseCaseAction { 4 | case loading 5 | case login(Result) 6 | } 7 | 8 | public protocol LoginUseCase { 9 | func login(userName: String, password: String) 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Boundary/Components/MyPage/MyPageUseCase.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | 3 | public enum MyPageUseCaseAction { 4 | case updateUser(User?) 5 | case logout 6 | } 7 | 8 | public protocol MyPageUseCase: Refreshable { 9 | func logout() 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Boundary/Components/User/Edit/UserEditUseCase.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | 3 | public enum UserEditUseCaseAction { 4 | case loading 5 | case edit(Result) 6 | } 7 | 8 | public protocol UserEditUseCase { 9 | func edit(userName: String, password: String) 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Boundary/Foundations/Refreshable.swift: -------------------------------------------------------------------------------- 1 | public protocol Refreshable: AnyObject { 2 | func refresh() 3 | } 4 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Boundary/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" == 5.1.1 2 | github "ra1028/Carbon" "fbe2c84c0e006051a8592624be6c32f87a10cd83" 3 | git "../../../Plan" == 0.2.0 4 | github "playbook-ui/playbook-ios" -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Cartfile.resolved: -------------------------------------------------------------------------------- 1 | git "../../../Plan" "0.2.0" 2 | github "ReactiveX/RxSwift" "5.1.1" 3 | github "playbook-ui/playbook-ios" "0.1.1" 4 | github "ra1028/Carbon" "fbe2c84c0e006051a8592624be6c32f87a10cd83" 5 | github "ra1028/DifferenceKit" "1.1.5" 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | final class AppDelegate: UIResponder, UIApplicationDelegate { 5 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 6 | UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Configs/Base.xcconfig: -------------------------------------------------------------------------------- 1 | TARGETED_DEVICE_FAMILY = 1 2 | SDKROOT = iphoneos 3 | SUPPORTED_PLATFORMS = iphonesimulator iphoneos 4 | DEVELOPMENT_LANGUAGE = Japan 5 | IPHONEOS_DEPLOYMENT_TARGET = 13.0 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Configs/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Provisioning/Development.xcconfig" 3 | //:configuration = Dev 4 | 5 | PRODUCT_DISPLAY_NAME = Catalog-Dev 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Configs/Prd.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Provisioning/Distribution.xcconfig" 3 | //:configuration = Prd 4 | 5 | PRODUCT_DISPLAY_NAME = Catalog 6 | CLANG_ENABLE_CODE_COVERAGE = NO 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Configs/Provisioning/Development.xcconfig: -------------------------------------------------------------------------------- 1 | CODE_SIGN_IDENTITY = iPhone Developer 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Configs/Provisioning/Distribution.xcconfig: -------------------------------------------------------------------------------- 1 | CODE_SIGN_IDENTITY = iPhone Distribution 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Configs/Stg.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Provisioning/Development.xcconfig" 3 | //:configuration = Stg 4 | 5 | PRODUCT_DISPLAY_NAME = Catalog-Stg 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Configs/Test.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Provisioning/Development.xcconfig" 3 | //:configuration = Test 4 | 5 | PRODUCT_DISPLAY_NAME = Catalog-Test 6 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Components/EntranceViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | 3 | extension EntranceViewController { 4 | static func mock() -> EntranceViewController { 5 | EntranceViewController(router: EntranceRouterMock()) 6 | } 7 | } 8 | 9 | final class EntranceRouterMock: EntranceRouting { 10 | func showHome() {} 11 | func showMyPage() {} 12 | } 13 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Components/HomeHeaderView+Mock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import Entity 3 | 4 | extension HomeHeaderView { 5 | static func mock(user: User?) -> HomeHeaderView { 6 | let view = HomeHeaderView.loadFromNib() 7 | view.render(with: .init(user: user)) 8 | return view 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Components/HomeViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Entity 4 | import RxSwift 5 | 6 | extension HomeViewController { 7 | static func mock(user: User?) -> HomeViewController { 8 | HomeViewController( 9 | presenter: HomePresenterMock(user: user), 10 | useCase: HomeUseCaseMock(), 11 | router: HomeRouterMock(), 12 | notification: UserStateNotificationMock() 13 | ) 14 | } 15 | } 16 | 17 | final class HomePresenterMock: HomePresenterProtocol { 18 | let dataState: Observable 19 | 20 | init(user: User?) { 21 | dataState = .just(.init(user: user)) 22 | } 23 | } 24 | 25 | final class HomeUseCaseMock: HomeUseCase { 26 | func refresh() {} 27 | } 28 | 29 | final class HomeRouterMock: HomeRouting {} 30 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Components/LoginViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import RxSwift 4 | 5 | extension LoginViewController { 6 | static func mock() -> LoginViewController { 7 | LoginViewController( 8 | presenter: LoginPresenterMock(), 9 | useCase: LoginUseCaseMock(), 10 | router: LoginRouterMock(), 11 | notification: UserStateNotificationMock() 12 | ) 13 | } 14 | } 15 | 16 | final class LoginPresenterMock: LoginPresenterProtocol { 17 | let viewModel: Observable = .just(.init()) 18 | let command: Observable = .empty() 19 | } 20 | 21 | final class LoginUseCaseMock: LoginUseCase { 22 | func login(userName: String, password: String) {} 23 | } 24 | 25 | final class LoginRouterMock: LoginRouting { 26 | func dismiss() {} 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Components/MyPageLabelView+Mock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | 3 | extension MyPageLabelView { 4 | static func mock(text: String) -> MyPageLabelView { 5 | let view = MyPageLabelView.loadFromNib() 6 | view.render(with: .init(text: text, tap: {})) 7 | return view 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Components/MyPageUserView+Mock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import Entity 3 | 4 | extension MyPageUserView { 5 | static func mock(user: User?) -> MyPageUserView { 6 | let view = MyPageUserView.loadFromNib() 7 | view.render(with: .init(user: user)) 8 | return view 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Components/MyPageViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Entity 4 | import RxSwift 5 | 6 | extension MyPageViewController { 7 | static func mock(user: User?) -> MyPageViewController { 8 | MyPageViewController( 9 | presenter: MyPagePresenterMock(user: user), 10 | useCase: MyPageUseCaseMock(), 11 | router: MyPageRouterMock(), 12 | notification: UserStateNotificationMock() 13 | ) 14 | } 15 | } 16 | 17 | final class MyPagePresenterMock: MyPagePresenterProtocol { 18 | let viewModel: Observable 19 | let command: Observable = .empty() 20 | 21 | init(user: User?) { 22 | viewModel = .just(.init(state: .init(user: user))) 23 | } 24 | } 25 | 26 | final class MyPageUseCaseMock: MyPageUseCase { 27 | func refresh() {} 28 | func logout() {} 29 | } 30 | 31 | final class MyPageRouterMock: MyPageRouting { 32 | func presentLogin() {} 33 | func pushUserEdit() {} 34 | func showHome(resetRequired: Bool) {} 35 | } 36 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Components/UserEditViewController+Mock.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import RxSwift 4 | 5 | extension UserEditViewController { 6 | static func mock() -> UserEditViewController { 7 | UserEditViewController( 8 | presenter: UserEditPresenterMock(), 9 | useCase: UserEditUseCaseMock(), 10 | router: UserEditRouterMock(), 11 | notification: UserStateNotificationMock() 12 | ) 13 | } 14 | } 15 | 16 | final class UserEditPresenterMock: UserEditPresenterProtocol { 17 | let viewModel: Observable = .just(.init()) 18 | let command: Observable = .empty() 19 | } 20 | 21 | final class UserEditUseCaseMock: UserEditUseCase { 22 | func edit(userName: String, password: String) {} 23 | } 24 | 25 | final class UserEditRouterMock: UserEditRouting { 26 | func pop() {} 27 | } 28 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Mocks/Foundations/UserStateNotificationMock.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import RxSwift 3 | 4 | final class UserStateNotificationMock: UserStateNotification { 5 | let userStateChanged: Observable = .empty() 6 | 7 | func postUserStateChanged() {} 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Resources/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 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Scenarios/AllScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | 3 | struct AllScenarios: ScenarioProvider { 4 | static func addScenarios(into playbook: Playbook) { 5 | playbook 6 | .add(EntranceScenarios.self) 7 | .add(HomeScenarios.self) 8 | .add(LoginScenarios.self) 9 | .add(MyPageScenarios.self) 10 | .add(UserEditScenarios.self) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Scenarios/EntranceScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct EntranceScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "Entrance") { 8 | Scenario("EntranceViewController", layout: .sizing(h: .fill, v: 1500)) { 9 | EntranceViewController.mock() 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Scenarios/HomeScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct HomeScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "Home") { 8 | Scenario("HomeHeaderView some User", layout: .fillH) { 9 | HomeHeaderView.mock(user: .init(id: "", name: "User Name", password: "password")) 10 | } 11 | 12 | Scenario("HomeHeaderView empty User", layout: .fillH) { 13 | HomeHeaderView.mock(user: nil) 14 | } 15 | 16 | Scenario("HomeViewController some User", layout: .sizing(h: .fill, v: 1500)) { 17 | HomeViewController.mock(user: .init(id: "", name: "User Name", password: "password")) 18 | } 19 | 20 | Scenario("HomeViewController empty User", layout: .sizing(h: .fill, v: 1500)) { 21 | HomeViewController.mock(user: nil) 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Scenarios/LoginScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct LoginScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "Login") { 8 | Scenario("LoginViewController", layout: .sizing(h: .fill, v: 1500)) { 9 | LoginViewController.mock() 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Scenarios/MyPageScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct MyPageScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "MyPage") { 8 | Scenario("MyPageLabelView", layout: .fillH) { 9 | MyPageLabelView.mock(text: "Scenario Test") 10 | } 11 | 12 | Scenario("MyPageUserView some User", layout: .fillH) { 13 | MyPageUserView.mock(user: .init(id: "", name: "User Name", password: "password")) 14 | } 15 | 16 | Scenario("MyPageUserView empty User", layout: .fillH) { 17 | MyPageUserView.mock(user: nil) 18 | } 19 | 20 | Scenario("MyPageViewController some User", layout: .sizing(h: .fill, v: 1500)) { 21 | MyPageViewController.mock(user: .init(id: "", name: "User Name", password: "password")) 22 | } 23 | 24 | Scenario("MyPageViewController empty User", layout: .sizing(h: .fill, v: 1500)) { 25 | MyPageViewController.mock(user: nil) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/Scenarios/UserEditScenarios.swift: -------------------------------------------------------------------------------- 1 | import Playbook 2 | import Component 3 | import SwiftUI 4 | 5 | struct UserEditScenarios: ScenarioProvider { 6 | static func addScenarios(into playbook: Playbook) { 7 | playbook.addScenarios(of: "UserEdit") { 8 | Scenario("UserEditViewController", layout: .sizing(h: .fill, v: 1500)) { 9 | UserEditViewController.mock() 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Catalog/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import PlaybookUI 2 | import SwiftUI 3 | 4 | struct PlaybookView: View { 5 | enum Tab { 6 | case catalog 7 | case gallery 8 | } 9 | 10 | @State 11 | var tab = Tab.gallery 12 | 13 | var body: some View { 14 | TabView(selection: $tab) { 15 | PlaybookGallery() 16 | .tag(Tab.gallery) 17 | .tabItem { 18 | Image(systemName: "rectangle.grid.3x2") 19 | Text("Gallery") 20 | } 21 | 22 | PlaybookCatalog() 23 | .tag(Tab.catalog) 24 | .tabItem { 25 | Image(systemName: "doc.text.magnifyingglass") 26 | Text("Catalog") 27 | } 28 | } 29 | } 30 | } 31 | 32 | final class SceneDelegate: UIResponder, UIWindowSceneDelegate { 33 | var window: UIWindow? 34 | 35 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 36 | guard let windowScene = scene as? UIWindowScene else { return } 37 | Playbook.default.add(AllScenarios.self) 38 | 39 | let window = UIWindow(windowScene: windowScene) 40 | window.rootViewController = UIHostingController(rootView: PlaybookView()) 41 | 42 | self.window = window 43 | window.makeKeyAndVisible() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/CatalogSnapshot/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/CatalogSnapshot/SnapshotTests.swift: -------------------------------------------------------------------------------- 1 | import PlaybookSnapshot 2 | import XCTest 3 | 4 | final class SnapshotTests: XCTestCase { 5 | func testTakeSnapshot() throws { 6 | guard let directory = ProcessInfo.processInfo.environment["SNAPSHOT_DIR"] else { 7 | fatalError("Set directory to the build environment variables with key `SNAPSHOT_DIR`.") 8 | } 9 | 10 | try Playbook.default.run( 11 | Snapshot( 12 | directory: URL(fileURLWithPath: directory), 13 | clean: true, 14 | format: .png, 15 | scale: 1, 16 | keyWindow: UIApplication.shared.windows.first { $0.isKeyWindow }, 17 | devices: [ 18 | .iPhone11Pro(.portrait), 19 | .iPhoneSE(.portrait) 20 | ] 21 | ) 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/Home/HomeComposer.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import Entity 3 | 4 | public struct HomeComposer { 5 | enum SectionID { 6 | case contents 7 | } 8 | 9 | public struct State { 10 | public var user: User? 11 | 12 | public init( 13 | user: User? = nil 14 | ) { 15 | self.user = user 16 | } 17 | } 18 | 19 | public init() {} 20 | 21 | public func compose(state: State) -> Group
{ 22 | Group { 23 | Section( 24 | id: SectionID.contents, 25 | cells: { 26 | HomeHeaderComponent(user: state.user) 27 | }) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/Home/HomeViewModel.swift: -------------------------------------------------------------------------------- 1 | public struct HomeViewModel { 2 | public var state: HomeComposer.State 3 | 4 | public init( 5 | state: HomeComposer.State = .init() 6 | ) { 7 | self.state = state 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/Home/Views/HomeHeaderView.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import Entity 3 | import UIKit 4 | 5 | public struct HomeHeaderComponent: IdentifiableComponent { 6 | public var user: User? 7 | 8 | public var id: String? { 9 | user?.id 10 | } 11 | 12 | public init(user: User?) { 13 | self.user = user 14 | } 15 | 16 | public func referenceSize(in bounds: CGRect) -> CGSize? { 17 | CGSize(width: bounds.width, height: 166) 18 | } 19 | 20 | public func shouldRender(next: HomeHeaderComponent, in content: HomeHeaderView) -> Bool { 21 | user?.name != next.user?.name 22 | } 23 | } 24 | 25 | public final class HomeHeaderView: UIView, NibLoadable, Renderable { 26 | @IBOutlet private weak var textLabel: UILabel! { 27 | didSet { 28 | textLabel.font = .systemFont(ofSize: 32) 29 | textLabel.textColor = .darkGray 30 | } 31 | } 32 | 33 | public func render(with component: HomeHeaderComponent) { 34 | textLabel.text = "Hello! " + (component.user?.name ?? "Guest") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/Login/LoginViewModel.swift: -------------------------------------------------------------------------------- 1 | public struct LoginViewModel { 2 | public enum Command { 3 | case loginCompleted 4 | } 5 | 6 | public var loginProgress: LoadProgress 7 | 8 | public init( 9 | loginProgress: LoadProgress = .default 10 | ) { 11 | self.loginProgress = loginProgress 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/MyPage/Entities/MyPageItem.swift: -------------------------------------------------------------------------------- 1 | public enum MyPageItem { 2 | case userEdit 3 | case login 4 | case logout 5 | 6 | public var text: String { 7 | switch self { 8 | case .userEdit: 9 | return "User Edit" 10 | 11 | case .login: 12 | return "Login" 13 | 14 | case .logout: 15 | return "Logout" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/MyPage/MyPageComposer.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import Entity 3 | 4 | public struct MyPageComposer { 5 | enum SectionID { 6 | case contents 7 | } 8 | 9 | public enum Event { 10 | case selectItem(MyPageItem) 11 | } 12 | 13 | public struct State { 14 | public var user: User? 15 | 16 | public init( 17 | user: User? = nil 18 | ) { 19 | self.user = user 20 | } 21 | } 22 | 23 | public var event: (Event) -> Void 24 | 25 | public init( 26 | event: @escaping (Event) -> Void 27 | ) { 28 | self.event = event 29 | } 30 | 31 | public func compose(state: State) -> Group
{ 32 | Group { 33 | Section( 34 | id: SectionID.contents, 35 | cells: { 36 | MyPageUserComponent(user: state.user) 37 | 38 | if state.user.isNone { 39 | MyPageLabelComponent(text: MyPageItem.login.text) { 40 | self.event(.selectItem(.login)) 41 | } 42 | } 43 | else { 44 | MyPageLabelComponent(text: MyPageItem.userEdit.text) { 45 | self.event(.selectItem(.userEdit)) 46 | } 47 | 48 | MyPageLabelComponent(text: MyPageItem.logout.text) { 49 | self.event(.selectItem(.logout)) 50 | } 51 | } 52 | }) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/MyPage/MyPageViewModel.swift: -------------------------------------------------------------------------------- 1 | public struct MyPageViewModel { 2 | public enum Command { 3 | case logoutCompleted 4 | } 5 | 6 | public var state: MyPageComposer.State 7 | 8 | public init( 9 | state: MyPageComposer.State = .init() 10 | ) { 11 | self.state = state 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/MyPage/Views/MyPageLabelView.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import RxSwift 3 | import UIKit 4 | 5 | public struct MyPageLabelComponent: IdentifiableComponent { 6 | public var text: String 7 | public var tap: () -> Void 8 | 9 | public var id: String { 10 | text 11 | } 12 | 13 | public init( 14 | text: String, 15 | tap: @escaping () -> Void 16 | ) { 17 | self.text = text 18 | self.tap = tap 19 | } 20 | 21 | public func referenceSize(in bounds: CGRect) -> CGSize? { 22 | CGSize(width: bounds.width, height: 60) 23 | } 24 | 25 | public func shouldRender(next: MyPageLabelComponent, in content: MyPageLabelView) -> Bool { 26 | text != next.text 27 | } 28 | } 29 | 30 | public final class MyPageLabelView: UIControl, NibLoadable, Renderable { 31 | @IBOutlet private weak var label: UILabel! { 32 | didSet { 33 | label.textColor = .darkGray 34 | label.font = .systemFont(ofSize: 24) 35 | } 36 | } 37 | 38 | private let disposeBag = DisposeBag() 39 | public var tap: (() -> Void)? 40 | 41 | public override func awakeFromNib() { 42 | super.awakeFromNib() 43 | 44 | rx.tap 45 | .subscribe(onNext: { [weak self] state in 46 | self?.tap?() 47 | }) 48 | .disposed(by: disposeBag) 49 | } 50 | 51 | public func render(with component: MyPageLabelComponent) { 52 | label.text = component.text 53 | tap = component.tap 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/MyPage/Views/MyPageUserView.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import Entity 3 | import UIKit 4 | 5 | public struct MyPageUserComponent: IdentifiableComponent { 6 | public var user: User? 7 | 8 | public var id: String? { 9 | user?.id 10 | } 11 | 12 | public init( 13 | user: User? 14 | ) { 15 | self.user = user 16 | } 17 | 18 | public func referenceSize(in bounds: CGRect) -> CGSize? { 19 | CGSize(width: bounds.width, height: 252) 20 | } 21 | 22 | public func shouldRender(next: MyPageUserComponent, in content: MyPageUserView) -> Bool { 23 | user?.name != next.user?.name 24 | } 25 | } 26 | 27 | public final class MyPageUserView: UIView, NibLoadable, Renderable { 28 | @IBOutlet private weak var imageView: UIImageView! { 29 | didSet { 30 | imageView.contentMode = .center 31 | imageView.tintColor = .white 32 | imageView.image = UIImage(named: "User", in: .component, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) 33 | imageView.backgroundColor = .lightGray 34 | imageView.layer.cornerRadius = imageView.bounds.height / 2 35 | } 36 | } 37 | 38 | @IBOutlet private weak var userNameLabel: UILabel! { 39 | didSet { 40 | userNameLabel.textAlignment = .center 41 | userNameLabel.font = .boldSystemFont(ofSize: 32) 42 | userNameLabel.textColor = .darkGray 43 | } 44 | } 45 | 46 | public func render(with component: MyPageUserComponent) { 47 | userNameLabel.text = component.user?.name ?? "Guest" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/NavigationController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol NavigationRouting: AnyObject { 4 | 5 | } 6 | 7 | public final class NavigationController: UINavigationController { 8 | public let router: NavigationRouting 9 | 10 | public init(router: NavigationRouting) { 11 | self.router = router 12 | super.init(nibName: nil, bundle: .component) 13 | } 14 | 15 | @available(*, unavailable) 16 | required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/RootViewController.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | import UIKit 3 | 4 | public protocol RootRouting: AnyObject { 5 | func showTabPages() 6 | func showEntrance() 7 | } 8 | 9 | public final class RootViewController: UIViewController { 10 | private let router: RootRouting 11 | private let disposeBag = DisposeBag() 12 | 13 | public init(router: RootRouting) { 14 | self.router = router 15 | super.init(nibName: nil, bundle: .component) 16 | } 17 | 18 | @available(*, unavailable) 19 | required init?(coder aDecoder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | public override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | rx.sentMessage(#selector(viewDidAppear)).map(value: ()) 27 | .take(1) 28 | .subscribe(onNext: { [weak self] _ in 29 | self?.router.showEntrance() 30 | }) 31 | .disposed(by: disposeBag) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/RootViewController.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 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/TabBarController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol TabRouting: AnyObject { 4 | 5 | } 6 | 7 | public final class TabBarController: UITabBarController { 8 | public let router: TabRouting 9 | 10 | public init(router: TabRouting) { 11 | self.router = router 12 | super.init(nibName: nil, bundle: .component) 13 | } 14 | 15 | @available(*, unavailable) 16 | required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | public override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Components/User/Edit/UserEditViewModel.swift: -------------------------------------------------------------------------------- 1 | public struct UserEditViewModel { 2 | public enum Command { 3 | case editCompleted 4 | } 5 | 6 | public var loginProgress: LoadProgress 7 | 8 | public init( 9 | loginProgress: LoadProgress = .default 10 | ) { 11 | self.loginProgress = loginProgress 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Extensions/Bundle+Token.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bundle { 4 | private final class Token {} 5 | 6 | static var component: Bundle { 7 | Bundle(for: Token.self) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Extensions/Optional+Utility.swift: -------------------------------------------------------------------------------- 1 | public extension Optional { 2 | var isSome: Bool { 3 | guard case .some = self else { return false } 4 | return true 5 | } 6 | 7 | var isNone: Bool { 8 | self == nil 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Extensions/Rx/ObservableType+Operator.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | 3 | public extension ObservableType { 4 | func map(value: V) -> Observable { 5 | map { _ in value } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Extensions/Rx/Refreshable+Rx.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import RxCocoa 3 | 4 | public extension Refreshable { 5 | var refresh: Binder { 6 | Binder(self) { base, _ in 7 | base.refresh() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Extensions/Rx/UIControl+Rx.swift: -------------------------------------------------------------------------------- 1 | import RxCocoa 2 | import RxSwift 3 | import UIKit 4 | 5 | extension Reactive where Base: UIControl { 6 | var tap: ControlEvent { 7 | controlEvent(.touchUpInside) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Extensions/UIKit/UITableView+Utility.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UITableView { 4 | override open func touchesShouldCancel(in view: UIView) -> Bool { 5 | !delaysContentTouches 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Extensions/UIKit/UIViewController+Utility.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension UIViewController { 4 | var isPresented: Bool { 5 | presentingViewController.isSome 6 | } 7 | 8 | func add(childController controller: UIViewController, to view: UIView? = nil) { 9 | addChild(controller) 10 | let parentView: UIView = view ?? self.view 11 | controller.view.frame = parentView.bounds 12 | parentView.addSubview(controller.view) 13 | controller.didMove(toParent: self) 14 | } 15 | 16 | func removeFromParentController() { 17 | willMove(toParent: nil) 18 | view.removeFromSuperview() 19 | removeFromParent() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Foundations/LoadProgress.swift: -------------------------------------------------------------------------------- 1 | public enum LoadProgress: Equatable { 2 | case `default` 3 | case loading 4 | case loadSucceeded 5 | case loadFailed 6 | 7 | public var isDefault: Bool { 8 | switch self { 9 | case .default: 10 | return true 11 | 12 | case .loading, 13 | .loadSucceeded, 14 | .loadFailed: 15 | return false 16 | } 17 | } 18 | 19 | public var isLoading: Bool { 20 | switch self { 21 | case .loading: 22 | return true 23 | 24 | case .default, 25 | .loadSucceeded, 26 | .loadFailed: 27 | return false 28 | } 29 | } 30 | 31 | public var isLoaded: Bool { 32 | switch self { 33 | case .loadSucceeded, .loadFailed: 34 | return true 35 | 36 | case .default, .loading: 37 | return false 38 | } 39 | } 40 | 41 | public var isLoadSucceeded: Bool { 42 | switch self { 43 | case .loadSucceeded: 44 | return true 45 | 46 | case .default, .loading, .loadFailed: 47 | return false 48 | } 49 | } 50 | 51 | public var isLoadFailed: Bool { 52 | switch self { 53 | case .loadFailed: 54 | return true 55 | 56 | case .default, .loading, .loadSucceeded: 57 | return false 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Foundations/NibBundler.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public protocol NibBundler: AnyObject { 4 | static var nib: UINib { get } 5 | static var nibName: String { get } 6 | static var nibBundle: Bundle? { get } 7 | } 8 | 9 | public extension NibBundler { 10 | static var nib: UINib { 11 | UINib(nibName: nibName, bundle: nibBundle) 12 | } 13 | 14 | static var nibName: String { 15 | String(describing: self) 16 | } 17 | 18 | static var nibBundle: Bundle? { 19 | Bundle(for: self) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Foundations/NibLoadable.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import UIKit 3 | 4 | public protocol NibLoadable: NibBundler {} 5 | 6 | public extension NibLoadable where Self: UIView { 7 | static func loadFromNib() -> Self { 8 | guard let view = nib.instantiate(withOwner: nil, options: nil).first as? Self else { 9 | fatalError("The nib \(nib) expected its root view to be of type \(self)") 10 | } 11 | return view 12 | } 13 | } 14 | 15 | public extension Component where Content: UIView & NibLoadable { 16 | func renderContent() -> Content { 17 | .loadFromNib() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Foundations/Notification/UserStateNotification.swift: -------------------------------------------------------------------------------- 1 | import RxSwift 2 | 3 | public protocol UserStateNotification: AnyObject { 4 | var userStateChanged: Observable { get } 5 | 6 | func postUserStateChanged() 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Foundations/Renderable.swift: -------------------------------------------------------------------------------- 1 | import Carbon 2 | import RxCocoa 3 | import RxSwift 4 | import UIKit 5 | 6 | public protocol Renderable: AnyObject { 7 | associatedtype Component 8 | 9 | func render(with component: Component) 10 | } 11 | 12 | extension Reactive where Base: Renderable { 13 | var render: Binder { 14 | Binder(base) { base, component in 15 | base.render(with: component) 16 | } 17 | } 18 | } 19 | 20 | public extension Component where Content: Renderable, Content.Component == Self { 21 | func render(in content: Content) { 22 | content.render(with: self) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Loading/LoadingViewController.swift: -------------------------------------------------------------------------------- 1 | import RxCocoa 2 | import RxSwift 3 | import UIKit 4 | 5 | public extension Reactive where Base: LoadingViewController { 6 | func presented(from controller: UIViewController) -> Binder { 7 | Binder(base) { [weak controller] base, isPresented in 8 | guard let controller = controller, base.isPresented != isPresented else { return } 9 | 10 | isPresented 11 | ? controller.present(base, animated: false) 12 | : base.dismiss(animated: false) 13 | } 14 | } 15 | } 16 | 17 | public final class LoadingViewController: UIViewController { 18 | @IBOutlet private weak var indicator: UIActivityIndicatorView! 19 | 20 | public required init() { 21 | super.init(nibName: nil, bundle: .component) 22 | 23 | modalPresentationStyle = .overFullScreen 24 | } 25 | 26 | @available(*, unavailable) 27 | public required init?(coder aDecoder: NSCoder) { 28 | fatalError("init(coder:) has not been implemented") 29 | } 30 | 31 | public override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | view.backgroundColor = UIColor.black.withAlphaComponent(0.2) 35 | } 36 | 37 | 38 | override public func viewWillAppear(_ animated: Bool) { 39 | super.viewWillAppear(animated) 40 | indicator.startAnimating() 41 | } 42 | 43 | override public func viewDidDisappear(_ animated: Bool) { 44 | super.viewDidDisappear(animated) 45 | indicator.stopAnimating() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Resources/Assets.xcassets/User.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "User.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Resources/Assets.xcassets/User.imageset/User.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KyoheiG3/Plan/49f53128eac924d9723b31ee4b0240d62bd0e12d/Examples/Example-iOS-Rx/Component/Resources/Assets.xcassets/User.imageset/User.pdf -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Component/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Configs/Base.xcconfig: -------------------------------------------------------------------------------- 1 | IPHONEOS_DEPLOYMENT_TARGET = 11.0 2 | SWIFT_VERSION = 5.0 3 | ARCHS = $ARCHS_STANDARD_64_BIT 4 | VALID_ARCHS = $ARCHS_STANDARD_64_BIT 5 | PROVISIONING_PROFILE_SPECIFIER = 6 | DEVELOPMENT_TEAM = 7 | CODE_SIGN_IDENTITY = 8 | CODE_SIGN_ENTITLEMENTS = 9 | CODE_SIGN_STYLE = Manual 10 | DEFINES_MODULE = NO 11 | PRODUCT_BUNDLE_IDENTIFIER = com.kyoheiito.Plan.Example-iOS-Rx.$(TARGET_NAME) 12 | DEBUG_INFORMATION_FORMAT = dwarf-with-dsym 13 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Configs/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | //:configuration = Dev 3 | 4 | OTHER_SWIFT_FLAGS = $(inherited) -DDEBUG 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Configs/Prd.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | //:configuration = Prd 3 | 4 | OTHER_SWIFT_FLAGS = $(inherited) -DRELEASE 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Configs/Stg.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | //:configuration = Stg 3 | 4 | OTHER_SWIFT_FLAGS = $(inherited) -DSTAGE 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Configs/Test.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | //:configuration = Test 3 | 4 | OTHER_SWIFT_FLAGS = $(inherited) -DTEST 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Entity/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Entity/User.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct User: Codable { 4 | public var id: String 5 | public var name: String 6 | public var password: String 7 | 8 | public init( 9 | id: String, 10 | name: String, 11 | password: String 12 | ) { 13 | self.id = id 14 | self.name = name 15 | self.password = password 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/EntityTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | final class AppDelegate: UIResponder, UIApplicationDelegate { 5 | lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds) 6 | lazy var router = AppRouter(container: window) 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 9 | router.start() 10 | 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Base.xcconfig: -------------------------------------------------------------------------------- 1 | TARGETED_DEVICE_FAMILY = 1 2 | SDKROOT = iphoneos 3 | SUPPORTED_PLATFORMS = iphonesimulator iphoneos 4 | DEVELOPMENT_LANGUAGE = Japan 5 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Environment/Dev.xcconfig" 3 | #include "./Provisioning/Development.xcconfig" 4 | //:configuration = Dev 5 | 6 | OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -warn-long-function-bodies=500 -Xfrontend -warn-long-expression-type-checking=200 7 | PRODUCT_DISPLAY_NAME = Clean-Dev 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Environment/Dev.xcconfig: -------------------------------------------------------------------------------- 1 | APS_ENVIRONMENT = development 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Environment/Prd.xcconfig: -------------------------------------------------------------------------------- 1 | APS_ENVIRONMENT = production 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Environment/Stg.xcconfig: -------------------------------------------------------------------------------- 1 | APS_ENVIRONMENT = development 2 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Prd.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Environment/Prd.xcconfig" 3 | #include "./Provisioning/Distribution.xcconfig" 4 | //:configuration = Prd 5 | 6 | PRODUCT_DISPLAY_NAME = Clean 7 | CLANG_ENABLE_CODE_COVERAGE = NO 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Provisioning/Development.xcconfig: -------------------------------------------------------------------------------- 1 | PRODUCT_BUNDLE_IDENTIFIER = com.kyoheiito.Plan.Example-iOS-Rx 2 | CODE_SIGN_IDENTITY = iPhone Developer 3 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Provisioning/Distribution.xcconfig: -------------------------------------------------------------------------------- 1 | PRODUCT_BUNDLE_IDENTIFIER = com.kyoheiito.Plan.Example-iOS-Rx 2 | CODE_SIGN_IDENTITY = iPhone Distribution 3 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Stg.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Environment/Stg.xcconfig" 3 | #include "./Provisioning/Development.xcconfig" 4 | //:configuration = Stg 5 | 6 | OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -warn-long-function-bodies=500 -Xfrontend -warn-long-expression-type-checking=200 7 | PRODUCT_DISPLAY_NAME = Clean-Stg 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Configs/Test.xcconfig: -------------------------------------------------------------------------------- 1 | #include "./Base.xcconfig" 2 | #include "./Environment/Dev.xcconfig" 3 | #include "./Provisioning/Development.xcconfig" 4 | //:configuration = Test 5 | 6 | PRODUCT_DISPLAY_NAME = Clean-Test 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Extensions/Bundle+AppInfo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bundle { 4 | struct AppInfo { 5 | var key: String 6 | 7 | init(key: String) { 8 | self.key = key 9 | } 10 | 11 | func object(for key: String) -> T? { 12 | Bundle.main.object(forInfoDictionaryKey: key) as? T 13 | } 14 | 15 | func toString() -> String { 16 | guard let string: String = object(for: key) else { 17 | fatalError(#""\#(key)" is not existing"#) 18 | } 19 | return string 20 | } 21 | } 22 | } 23 | 24 | extension Bundle.AppInfo { 25 | static var id: String { 26 | Bundle.main.bundleIdentifier ?? Bundle.AppInfo(key: "CFBundleIdentifier").toString() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Extensions/Gateway/Repositories/UserRepository.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Gateway 3 | import RxSwift 4 | 5 | extension UserRepository.Dependency { 6 | static let `default` = Self( 7 | scheduler: MainScheduler.instance, 8 | userDefaults: UserDefaults.standard, 9 | uuidGen: UUID() 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Extensions/Notification/NotificationCenter+UserState.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import Foundation 3 | import RxSwift 4 | 5 | extension NotificationCenter: UserStateNotification { 6 | public var userStateChanged: Observable { 7 | rx.notification(.userStateChanged) 8 | .map(value: ()) 9 | } 10 | 11 | public func postUserStateChanged() { 12 | post(name: .userStateChanged, object: nil) 13 | } 14 | } 15 | 16 | private extension Notification.Name { 17 | static let userStateChanged = Notification.Name(Bundle.AppInfo.id + ".UserStateChanged") 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Extensions/UIKit/UITabBarController+Utility.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UITabBarController { 4 | var selectedBarItemTag: Int? { 5 | get { 6 | selectedViewController?.tabBarItem.tag 7 | } 8 | set { 9 | selectedViewController = viewControllers?.first { controller in 10 | controller.tabBarItem.tag == newValue 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Extensions/UseCase/Interactors/HomeInteractor.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import UseCase 3 | 4 | extension HomeInteractor.Dependency { 5 | static var `default` = Self( 6 | userRepository: UserRepository(dependency: .default) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Extensions/UseCase/Interactors/LoginInteractor.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import UseCase 3 | 4 | extension LoginInteractor.Dependency { 5 | static var `default` = Self( 6 | userRepository: UserRepository(dependency: .default) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Extensions/UseCase/Interactors/MyPageInteractor.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import UseCase 3 | 4 | extension MyPageInteractor.Dependency { 5 | static var `default` = Self( 6 | userRepository: UserRepository(dependency: .default) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Extensions/UseCase/Interactors/UserEditInteractor.Dependency+DI.swift: -------------------------------------------------------------------------------- 1 | import Gateway 2 | import UseCase 3 | 4 | extension UserEditInteractor.Dependency { 5 | static var `default` = Self( 6 | userRepository: UserRepository(dependency: .default) 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Resources/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 | 27 | 28 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Router/AppRouter.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import UIKit 3 | 4 | final class AppRouter: RootRouting { 5 | private(set) weak var container: UIWindow? 6 | private var rootRouter: RootRouter? 7 | 8 | init(container: UIWindow?) { 9 | self.container = container 10 | } 11 | 12 | func start() { 13 | let controller = RootViewController(router: self) 14 | rootRouter = RootRouter(container: controller) 15 | container?.rootViewController = controller 16 | container?.makeKeyAndVisible() 17 | } 18 | 19 | func showTabPages() { 20 | rootRouter?.showTabPages(initialTab: .home) 21 | } 22 | 23 | func showEntrance() { 24 | rootRouter?.showEntrance() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Router/NavigationRouter.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import Foundation 3 | import Presentation 4 | import UseCase 5 | 6 | final class NavigationRouter: NavigationRouting, MyPageRouting, HomeRouting, LoginRouting, UserEditRouting { 7 | private weak var container: NavigationController? 8 | private weak var tabRouter: TabRouter? 9 | 10 | init(container: NavigationController?, tabRouter: TabRouter?) { 11 | self.container = container 12 | self.tabRouter = tabRouter 13 | } 14 | 15 | func pushUserEdit() { 16 | let presenter = UserEditPresenter(store: .init(), translator: .init()) 17 | let interactor = UserEditInteractor(dispatcher: presenter.asDispatcher(), dependency: .default) 18 | let controller = UserEditViewController(presenter: presenter, useCase: interactor, router: self, notification: NotificationCenter.default) 19 | container?.pushViewController(controller, animated: true) 20 | } 21 | 22 | func presentLogin() { 23 | let navigation = NavigationController(router: self) 24 | let router = NavigationRouter(container: navigation, tabRouter: tabRouter) 25 | let presenter = LoginPresenter(store: .init(), translator: .init()) 26 | let interactor = LoginInteractor(dispatcher: presenter.asDispatcher(), dependency: .default) 27 | let controller = LoginViewController(presenter: presenter, useCase: interactor, router: router, notification: NotificationCenter.default) 28 | navigation.viewControllers = [controller] 29 | container?.present(navigation, animated: true) 30 | } 31 | 32 | func showHome(resetRequired: Bool = true) { 33 | tabRouter?.showHome(resetRequired: resetRequired) 34 | } 35 | 36 | func dismiss() { 37 | container?.dismiss(animated: true) 38 | } 39 | 40 | func pop() { 41 | container?.popViewController(animated: true) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Example-iOS-Rx/Router/RootRouter.swift: -------------------------------------------------------------------------------- 1 | import Component 2 | import UIKit 3 | 4 | final class RootRouter: TabRouting, NavigationRouting, EntranceRouting { 5 | private(set) weak var container: RootViewController? 6 | private lazy var tabContainer = TabBarController(router: self) 7 | private lazy var tabRouter = TabRouter(container: tabContainer, rootRouter: self) 8 | 9 | private var currentController: UIViewController? { 10 | didSet { 11 | oldValue?.removeFromParentController() 12 | guard let newValue = currentController else { 13 | return 14 | } 15 | 16 | container?.add(childController: newValue) 17 | } 18 | } 19 | 20 | init(container: RootViewController?) { 21 | self.container = container 22 | } 23 | 24 | func showTabPages(initialTab: RootTab) { 25 | tabRouter.resetTabPages() 26 | tabRouter.select(tab: initialTab) 27 | currentController = tabRouter.container 28 | } 29 | 30 | func showEntrance() { 31 | let controller = EntranceViewController(router: self) 32 | container?.present(controller, animated: false) 33 | } 34 | 35 | func showHome() { 36 | showTabPages(initialTab: .home) 37 | container?.dismiss(animated: true) 38 | } 39 | 40 | func showMyPage() { 41 | showTabPages(initialTab: .myPage) 42 | container?.dismiss(animated: true) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Gateway/Foundations/DefaultsKey.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct DefaultsKey { 4 | fileprivate var key: String 5 | 6 | static let token = DefaultsKey(key: "token") 7 | static let user = DefaultsKey(key: "user") 8 | } 9 | 10 | extension UserDefaults { 11 | func set(_ value: Any?, forKey key: DefaultsKey) { 12 | self.set(value, forKey: key.key) 13 | } 14 | 15 | func data(forKey key: DefaultsKey) -> Data? { 16 | self.data(forKey: key.key) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Gateway/Foundations/UUIDGen.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol UUIDGen { 4 | var uuidString: String { get } 5 | } 6 | 7 | extension UUID: UUIDGen {} 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Gateway/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Gateway/Repositories/UserRepository.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | import Foundation 3 | import RxSwift 4 | import UseCase 5 | 6 | public final class UserRepository: UserRepositoryProtocol { 7 | public struct Dependency { 8 | public var scheduler: SchedulerType 9 | public var userDefaults: UserDefaults 10 | public var uuidGen: UUIDGen 11 | 12 | public init( 13 | scheduler: SchedulerType, 14 | userDefaults: UserDefaults, 15 | uuidGen: UUIDGen 16 | ) { 17 | self.scheduler = scheduler 18 | self.userDefaults = userDefaults 19 | self.uuidGen = uuidGen 20 | } 21 | } 22 | 23 | private let dependency: Dependency 24 | 25 | public init(dependency: Dependency) { 26 | self.dependency = dependency 27 | } 28 | 29 | public func loginUser() -> User? { 30 | try? dependency.userDefaults.data(forKey: .user).map { 31 | try JSONDecoder().decode(User.self, from: $0) 32 | } 33 | } 34 | 35 | public func login(userName: String, password: String) -> Observable { 36 | let dependency = self.dependency 37 | return Observable.create { observer in 38 | let user = User( 39 | id: dependency.uuidGen.uuidString, 40 | name: userName, 41 | password: password 42 | ) 43 | 44 | dependency.userDefaults.set(user.id, forKey: .token) 45 | 46 | let data = try? JSONEncoder().encode(user) 47 | dependency.userDefaults.set(data, forKey: .user) 48 | observer.onNext(user) 49 | observer.onCompleted() 50 | return Disposables.create() 51 | } 52 | .delay(.seconds(1), scheduler: dependency.scheduler) 53 | } 54 | 55 | public func logout() { 56 | dependency.userDefaults.set(nil, forKey: .token) 57 | dependency.userDefaults.set(nil, forKey: .user) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/GatewayTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Makefile: -------------------------------------------------------------------------------- 1 | carthageModule := 2 | 3 | # XcodeGen 4 | 5 | .PHONY: run-xcodegen 6 | run-xcodegen: 7 | xcodegen 8 | 9 | # Carthage 10 | 11 | .PHONY: bootstrap-carthage 12 | bootstrap-carthage: 13 | carthage bootstrap --platform ios --use-ssh --no-use-binaries --cache-builds 14 | 15 | .PHONY: update-carthage 16 | update-carthage: 17 | carthage update --platform ios --use-ssh --no-use-binaries --cache-builds ${carthageModule} 18 | 19 | .PHONY: renew-carthage 20 | renew-carthage: 21 | carthage update --platform ios --use-ssh --no-use-binaries 22 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Presentation/Components/Home/HomePresenter.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Plan 4 | import RxRelay 5 | import RxSwift 6 | 7 | public final class HomePresenter: Presenter, HomePresenterProtocol { 8 | public var dataState: Observable { 9 | store.viewModel.map(\.state) 10 | } 11 | } 12 | 13 | public final class HomeStore: Store { 14 | fileprivate let viewModel = BehaviorRelay(value: HomeViewModel()) 15 | 16 | public init() {} 17 | } 18 | 19 | public struct HomeTranslator: Translator { 20 | public init() {} 21 | 22 | public func translate(action: HomeUseCaseAction, store: HomeStore) { 23 | switch action { 24 | case .updateUser(let user): 25 | store.viewModel.modefy { viewModel in 26 | viewModel.state.user = user 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Presentation/Components/Login/LoginPresenter.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Plan 4 | import RxRelay 5 | import RxSwift 6 | 7 | public final class LoginPresenter: Presenter, LoginPresenterProtocol { 8 | public var viewModel: Observable { 9 | store.viewModel.asObservable() 10 | } 11 | 12 | public var command: Observable { 13 | store.command.asObservable() 14 | } 15 | } 16 | 17 | public final class LoginStore: Store { 18 | fileprivate let viewModel = BehaviorRelay(value: LoginViewModel()) 19 | fileprivate let command = PublishRelay() 20 | 21 | public init() {} 22 | } 23 | 24 | public struct LoginTranslator: Translator { 25 | public init() {} 26 | 27 | public func translate(action: LoginUseCaseAction, store: LoginStore) { 28 | switch action { 29 | case .loading: 30 | store.viewModel.modefy { viewModel in 31 | viewModel.loginProgress = .loading 32 | } 33 | 34 | case .login(.success): 35 | store.viewModel.modefy { viewModel in 36 | viewModel.loginProgress = .loadSucceeded 37 | } 38 | store.command.accept(.loginCompleted) 39 | 40 | case .login(.failure(let error)): 41 | print("login failed \(error)") 42 | store.viewModel.modefy { viewModel in 43 | viewModel.loginProgress = .loadFailed 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Presentation/Components/MyPage/MyPagePresenter.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Plan 4 | import RxRelay 5 | import RxSwift 6 | 7 | public final class MyPagePresenter: Presenter, MyPagePresenterProtocol { 8 | public var viewModel: Observable { 9 | store.viewModel.asObservable() 10 | } 11 | 12 | public var command: Observable { 13 | store.command.asObservable() 14 | } 15 | } 16 | 17 | public final class MyPageStore: Store { 18 | fileprivate let viewModel = BehaviorRelay(value: MyPageViewModel()) 19 | fileprivate let command = PublishRelay() 20 | 21 | public init() {} 22 | } 23 | 24 | public struct MyPageTranslator: Translator { 25 | public init() {} 26 | 27 | public func translate(action: MyPageUseCaseAction, store: MyPageStore) { 28 | switch action { 29 | case .updateUser(let user): 30 | store.viewModel.modefy { viewModel in 31 | viewModel.state.user = user 32 | } 33 | 34 | case .logout: 35 | store.command.accept(.logoutCompleted) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Presentation/Components/User/Edit/UserEditPresenter.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Component 3 | import Plan 4 | import RxRelay 5 | import RxSwift 6 | 7 | public final class UserEditPresenter: Presenter, UserEditPresenterProtocol { 8 | public var viewModel: Observable { 9 | store.viewModel.asObservable() 10 | } 11 | 12 | public var command: Observable { 13 | store.command.asObservable() 14 | } 15 | } 16 | 17 | public final class UserEditStore: Store { 18 | fileprivate let viewModel = BehaviorRelay(value: UserEditViewModel()) 19 | fileprivate let command = PublishRelay() 20 | 21 | public init() {} 22 | } 23 | 24 | public struct UserEditTranslator: Translator { 25 | public init() {} 26 | 27 | public func translate(action: UserEditUseCaseAction, store: UserEditStore) { 28 | switch action { 29 | case .loading: 30 | store.viewModel.modefy { viewModel in 31 | viewModel.loginProgress = .loading 32 | } 33 | 34 | case .edit(.success): 35 | store.viewModel.modefy { viewModel in 36 | viewModel.loginProgress = .loadSucceeded 37 | } 38 | store.command.accept(.editCompleted) 39 | 40 | case .edit(.failure(let error)): 41 | print("edit failed \(error)") 42 | store.viewModel.modefy { viewModel in 43 | viewModel.loginProgress = .loadFailed 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Presentation/Extensions/Rx/BehaviorRelay+Modify.swift: -------------------------------------------------------------------------------- 1 | import RxRelay 2 | 3 | extension BehaviorRelay { 4 | @discardableResult 5 | func modefy(block: (inout Element) -> Void) -> Element { 6 | var newValue = value 7 | block(&newValue) 8 | accept(newValue) 9 | return newValue 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Presentation/Extensions/Translator+Executor.swift: -------------------------------------------------------------------------------- 1 | import Plan 2 | 3 | extension Translator { 4 | public var executor: Executor { 5 | .main 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/Presentation/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/PresentationTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/README.md: -------------------------------------------------------------------------------- 1 | # Example-iOS-Rx 2 | 3 | [Read me](../README.md) about this Example. 4 | 5 | ## Requirements 6 | 7 | - Swift 5 8 | - iOS 11.0 or later 9 | 10 | ## Usage 11 | 12 | Install some tools to build the project. 13 | 14 | #### Carthage 15 | 16 | ```bash 17 | $ brew install carthage 18 | ``` 19 | 20 | #### XcodeGen 21 | 22 | ```bash 23 | $ brew install xcodegen 24 | ``` 25 | 26 | Execute the following commands. 27 | 28 | ```bash 29 | $ make bootstrap-carthage 30 | $ make run-xcodegen 31 | $ open Example-iOS-Rx.xcodeproj 32 | ``` 33 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/UseCase/Components/Home/HomeInteractor.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Plan 3 | import RxSwift 4 | 5 | public final class HomeInteractor: Interactor, HomeUseCase { 6 | public struct Dependency { 7 | public var userRepository: UserRepositoryProtocol 8 | 9 | public init( 10 | userRepository: UserRepositoryProtocol 11 | ) { 12 | self.userRepository = userRepository 13 | } 14 | } 15 | 16 | private let dependency: Dependency 17 | 18 | public init(dispatcher: D, dependency: Dependency) where D.Action == Action { 19 | self.dependency = dependency 20 | super.init(dispatcher: dispatcher) 21 | } 22 | 23 | public func refresh() { 24 | dispatcher.dispatch(.updateUser(dependency.userRepository.loginUser())) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/UseCase/Components/Login/LoginInteractor.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Plan 3 | import RxSwift 4 | 5 | public final class LoginInteractor: Interactor, LoginUseCase { 6 | public struct Dependency { 7 | public var userRepository: UserRepositoryProtocol 8 | 9 | public init( 10 | userRepository: UserRepositoryProtocol 11 | ) { 12 | self.userRepository = userRepository 13 | } 14 | } 15 | 16 | private let dependency: Dependency 17 | private let loginDisposeBag = DisposeBag() 18 | 19 | public init(dispatcher: D, dependency: Dependency) where D.Action == Action { 20 | self.dependency = dependency 21 | super.init(dispatcher: dispatcher) 22 | } 23 | 24 | public func login(userName: String, password: String) { 25 | dispatcher.dispatch(.loading) 26 | dependency.userRepository.login(userName: userName, password: password) 27 | .subscribe(onNext: { [weak self] user in 28 | self?.dispatcher.dispatch(.login(.success(user))) 29 | }, onError: { [weak self] error in 30 | self?.dispatcher.dispatch(.login(.failure(error))) 31 | }) 32 | .disposed(by: loginDisposeBag) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/UseCase/Components/MyPage/MyPageInteractor.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Plan 3 | import RxSwift 4 | 5 | public final class MyPageInteractor: Interactor, MyPageUseCase { 6 | public struct Dependency { 7 | public var userRepository: UserRepositoryProtocol 8 | 9 | public init( 10 | userRepository: UserRepositoryProtocol 11 | ) { 12 | self.userRepository = userRepository 13 | } 14 | } 15 | 16 | private let dependency: Dependency 17 | 18 | public init(dispatcher: D, dependency: Dependency) where D.Action == Action { 19 | self.dependency = dependency 20 | super.init(dispatcher: dispatcher) 21 | } 22 | 23 | public func refresh() { 24 | dispatcher.dispatch(.updateUser(dependency.userRepository.loginUser())) 25 | } 26 | 27 | public func logout() { 28 | dependency.userRepository.logout() 29 | dispatcher.dispatch(.logout) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/UseCase/Components/User/Edit/UserEditInteractor.swift: -------------------------------------------------------------------------------- 1 | import Boundary 2 | import Plan 3 | import RxSwift 4 | 5 | public final class UserEditInteractor: Interactor, UserEditUseCase { 6 | public struct Dependency { 7 | public var userRepository: UserRepositoryProtocol 8 | 9 | public init( 10 | userRepository: UserRepositoryProtocol 11 | ) { 12 | self.userRepository = userRepository 13 | } 14 | } 15 | 16 | private let dependency: Dependency 17 | private let editDisposeBag = DisposeBag() 18 | 19 | public init(dispatcher: D, dependency: Dependency) where D.Action == Action { 20 | self.dependency = dependency 21 | super.init(dispatcher: dispatcher) 22 | } 23 | 24 | public func edit(userName: String, password: String) { 25 | dispatcher.dispatch(.loading) 26 | dependency.userRepository.login(userName: userName, password: password) 27 | .subscribe(onNext: { [weak self] user in 28 | self?.dispatcher.dispatch(.edit(.success(user))) 29 | }, onError: { [weak self] error in 30 | self?.dispatcher.dispatch(.edit(.failure(error))) 31 | }) 32 | .disposed(by: editDisposeBag) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/UseCase/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/UseCase/Repositories/UserRepository.swift: -------------------------------------------------------------------------------- 1 | import Entity 2 | import Foundation 3 | import RxSwift 4 | 5 | public protocol UserRepositoryProtocol { 6 | func loginUser() -> User? 7 | func login(userName: String, password: String) -> Observable 8 | func logout() 9 | } 10 | -------------------------------------------------------------------------------- /Examples/Example-iOS-Rx/UseCaseTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/README.md: -------------------------------------------------------------------------------- 1 | # Plan Examples 2 | 3 | ## Overview 4 | 5 | This example uses `Plan` framework to implement a clean architecture. 6 | 7 | With the following structure, it is possible to reduce the dependency of the component and clear the scope of development and testing. 8 | 9 | ![Plan Example Structure](https://user-images.githubusercontent.com/5707132/91378757-30b49e00-e85c-11ea-9884-2bc5503b8e8e.png) 10 | 11 | 12 | `Component` is composed of user interface related objects such as `UIView` and `UIViewController`. 13 | 14 | By limiting the dependency of `Component` to `Boundary` (interface) and `Entity`, visual regression test, etc. can be easily executed. 15 | 16 | ## Structure 17 | 18 | This project is structured 2 apps and 5 frameworks. All frameworks are divided into layers by role so that only the modules needed for the application are imported. 19 | 20 | The application is divided into `Example.app` which is executed as a product and `Catalog.app` which is executed to test the UI. The product application imports all modules, but the test application imports only the `Component.framework`, `Boundary.framework`, and `Entity.framework` required for testing. By making UI components into independent modules, `Catalog` application can import and execute only the modules required for UI configuration. 21 | 22 | `Boundary` consists of the interface of application logic and the object to execute processing (eg aggregate response etc). 23 | 24 | ## Projects 25 | 26 | #### Example-iOS-RAC 27 | 28 | It depends on `ReactiveSwift`. 29 | 30 | #### Example-iOS-Rx 31 | 32 | It depends on `RxSwift`. 33 | -------------------------------------------------------------------------------- /Examples/plan.pu: -------------------------------------------------------------------------------- 1 | @startuml Plan Example Structure 2 | 3 | title Plan Example Structure 4 | 5 | file Example 6 | note right : Example App 7 | 8 | file Catalog 9 | note right : UITest App 10 | 11 | frame Presentation <> { 12 | [Presenter] 13 | [Component] <> 14 | } 15 | 16 | frame Gateway <> { 17 | [Repository] 18 | } 19 | 20 | frame UseCase <> { 21 | [Interactor] 22 | [Repository] 23 | [Boundary] <> 24 | } 25 | 26 | frame Domain { 27 | [Entity] <> 28 | } 29 | 30 | Example --> Presentation 31 | Example --> Gateway 32 | Example ---> UseCase 33 | Gateway --> UseCase 34 | 35 | Example --> Component 36 | Catalog --> Component 37 | Presenter -> Component 38 | 39 | Interactor .> [Boundary] 40 | Presenter --> [Boundary] 41 | Component --> [Boundary] 42 | 43 | Repository ..> [Repository] 44 | Interactor --> [Repository] 45 | 46 | Component --> Entity 47 | [Repository] --> Entity 48 | [Boundary] -> Entity 49 | 50 | @enduml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kyohei Ito 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Plan", 8 | platforms: [.iOS(.v10)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, and make them visible to other packages. 11 | .library( 12 | name: "Plan", 13 | targets: ["Plan"]), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | // .package(url: /* package url */, from: "1.0.0"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 22 | .target( 23 | name: "Plan", 24 | dependencies: [], path: "Plan"), 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /Plan.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'Plan' 3 | s.version = '0.2.1' 4 | s.swift_version = '5.0' 5 | s.summary = 'The Plan.framework helps to keep your iOS application design clean.' 6 | s.homepage = 'https://github.com/KyoheiG3/Plan' 7 | s.license = { :type => 'MIT', :file => 'LICENSE' } 8 | s.author = { 'Kyohei Ito' => 'ito_kyohei@cyberagent.co.jp' } 9 | s.source = { :git => 'https://github.com/KyoheiG3/Plan.git', :tag => s.version.to_s } 10 | s.ios.deployment_target = '10.0' 11 | s.tvos.deployment_target = '10.0' 12 | s.osx.deployment_target = '10.12' 13 | s.watchos.deployment_target = '3.0' 14 | s.source_files = 'Plan/**/*.{h,swift}' 15 | s.requires_arc = true 16 | end 17 | -------------------------------------------------------------------------------- /Plan.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Plan.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Plan/Atomic.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class Atomic { 4 | private var _value: Value 5 | private var lock = os_unfair_lock_s() 6 | 7 | init(_ value: Value) { 8 | _value = value 9 | } 10 | 11 | func callAsFunction() -> Value { 12 | defer { os_unfair_lock_unlock(&lock) } 13 | os_unfair_lock_lock(&lock) 14 | return _value 15 | } 16 | 17 | @discardableResult 18 | func modify(block: (inout Value) -> Void) -> Value { 19 | defer { os_unfair_lock_unlock(&lock) } 20 | os_unfair_lock_lock(&lock) 21 | block(&_value) 22 | return _value 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Plan/Dispatchable.swift: -------------------------------------------------------------------------------- 1 | public protocol Dispatchable { 2 | associatedtype Action 3 | 4 | func dispatch(_ action: Action) 5 | func asDispatcher() -> AnyDispatcher 6 | } 7 | 8 | public class AnyDispatcher: Dispatchable { 9 | private let _dispatch: (Action) -> Void 10 | 11 | public init(_ dispatcher: D) where D.Action == Action { 12 | _dispatch = dispatcher.dispatch 13 | } 14 | 15 | public init(dispatch: @escaping (Action) -> Void) { 16 | _dispatch = dispatch 17 | } 18 | 19 | public func dispatch(_ action: Action) { 20 | _dispatch(action) 21 | } 22 | 23 | public func asDispatcher() -> AnyDispatcher { 24 | self 25 | } 26 | 27 | public func map(block: @escaping (U) -> Action) -> AnyDispatcher { 28 | let dispatch = _dispatch 29 | return AnyDispatcher { action in 30 | dispatch(block(action)) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Plan/Dispatcher.swift: -------------------------------------------------------------------------------- 1 | final class Dispatcher: Dispatchable { 2 | private let executor: Executor 3 | 4 | deinit { 5 | executor.cancel() 6 | } 7 | 8 | convenience init(store: T.Store, translator: T) where T.Action == Action { 9 | self.init(store: store, translator: translator, executor: translator.executor) 10 | } 11 | 12 | init(store: T.Store, translator: T, executor: Executor) where T.Action == Action { 13 | executor.job { 14 | translator.translate(action: $0, store: store) 15 | } 16 | 17 | self.executor = executor 18 | } 19 | 20 | func dispatch(_ action: Action) { 21 | executor.execute(work: action) 22 | } 23 | 24 | func asDispatcher() -> AnyDispatcher { 25 | AnyDispatcher(self) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Plan/Executor.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | public final class Executor { 4 | public static var immediate: Executor { 5 | .init(ImmediateQueue()) 6 | } 7 | 8 | public static var main: Executor { 9 | .init(MainThreadQueue()) 10 | } 11 | 12 | public static func queue(_ queue: DispatchQueue = .global()) -> Executor { 13 | .init(queue) 14 | } 15 | 16 | let queue: Queue 17 | private let isCancelled = Atomic(false) 18 | private var _execute: ((Value) -> Void)? 19 | 20 | init(_ queue: Queue) { 21 | self.queue = queue 22 | } 23 | 24 | func job(_ job: @escaping (Value) -> Void) { 25 | guard !isCancelled() else { return } 26 | _execute = job 27 | } 28 | 29 | func execute(work value: Value) { 30 | queue.execute { [weak self] in 31 | guard let me = self, let execute = me._execute, !me.isCancelled() else { return } 32 | execute(value) 33 | } 34 | } 35 | 36 | func cancel() { 37 | isCancelled.modify { $0 = true } 38 | _execute = nil 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Plan/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Plan/Interactor.swift: -------------------------------------------------------------------------------- 1 | open class Interactor { 2 | public typealias Action = Action 3 | public let dispatcher: AnyDispatcher 4 | 5 | public init(dispatcher: D) where D.Action == Action { 6 | if let dispatcher = dispatcher as? AnyDispatcher { 7 | self.dispatcher = dispatcher 8 | } 9 | else { 10 | self.dispatcher = AnyDispatcher(dispatcher) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Plan/Plan.h: -------------------------------------------------------------------------------- 1 | // 2 | // Plan.h 3 | // Plan 4 | // 5 | // Created by Kyohei Ito on 2020/06/16. 6 | // Copyright © 2020 Kyohei Ito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Plan. 12 | FOUNDATION_EXPORT double PlanVersionNumber; 13 | 14 | //! Project version string for Plan. 15 | FOUNDATION_EXPORT const unsigned char PlanVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Plan/Presenter.swift: -------------------------------------------------------------------------------- 1 | open class Presenter: Dispatchable { 2 | public let store: Translator.Store 3 | let dispatcher: Dispatcher 4 | let translator: Translator 5 | 6 | public init(store: Translator.Store, translator: Translator) { 7 | self.store = store 8 | self.translator = translator 9 | self.dispatcher = Dispatcher(store: store, translator: translator) 10 | } 11 | 12 | public func asDispatcher() -> AnyDispatcher { 13 | AnyDispatcher(self) 14 | } 15 | 16 | public func dispatch(_ action: Translator.Action) { 17 | dispatcher.dispatch(action) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Plan/Queues/ImmediateQueue.swift: -------------------------------------------------------------------------------- 1 | final class ImmediateQueue: Queue { 2 | func execute(_ work: () -> Void) { 3 | work() 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Plan/Queues/MainThreadQueue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class MainThreadQueue: Queue { 4 | let counter = Atomic(0) 5 | 6 | func execute(_ work: @escaping () -> Void) { 7 | let count = counter.modify { $0 += 1 } 8 | 9 | if Thread.isMainThread && count == 1 { 10 | work() 11 | counter.modify { $0 -= 1 } 12 | } else { 13 | DispatchQueue.main.async { 14 | work() 15 | self.counter.modify { $0 -= 1 } 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Plan/Queues/Queue.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol Queue { 4 | func execute(_ work: @escaping () -> Void) 5 | } 6 | 7 | extension DispatchQueue: Queue { 8 | func execute(_ work: @escaping () -> Void) { 9 | async(execute: work) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Plan/Store.swift: -------------------------------------------------------------------------------- 1 | public protocol Store: AnyObject {} 2 | -------------------------------------------------------------------------------- /Plan/Translator.swift: -------------------------------------------------------------------------------- 1 | public protocol Translator { 2 | associatedtype Action 3 | associatedtype Store: Plan.Store 4 | 5 | var executor: Executor { get } 6 | 7 | func translate(action: Action, store: Store) 8 | } 9 | 10 | public extension Translator { 11 | var executor: Executor { 12 | .immediate 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PlanTests/AtomicTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Plan 3 | 4 | private final class AtomicTests: XCTestCase { 5 | func testModify() { 6 | let counter = Atomic(0) 7 | 8 | DispatchQueue.concurrentPerform(iterations: 1000) { _ in 9 | counter.modify { $0 += 1 } 10 | } 11 | 12 | XCTAssertEqual(counter(), 1000) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /PlanTests/DispatcherTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Plan 3 | 4 | private final class DispatcherTests: XCTestCase { 5 | func testAnyDispatcher() { 6 | var action: MockAction? 7 | let dispatcher = AnyDispatcher { action = $0 } 8 | 9 | let anyDispatcher = AnyDispatcher(dispatcher) 10 | anyDispatcher.dispatch(.test) 11 | 12 | XCTAssertEqual(action, .test) 13 | } 14 | 15 | func testDispatcherMap() { 16 | var action: DispatcherMock.Action? 17 | let dispatcher = DispatcherMock { action = $0 } 18 | 19 | let anyDispatcher: AnyDispatcher = dispatcher.asDispatcher() 20 | .map { action -> DispatcherMock.Action in 21 | switch action { 22 | case MockAction.test: 23 | return DispatcherMock.Action.test 24 | } 25 | } 26 | 27 | anyDispatcher.dispatch(MockAction.test) 28 | 29 | XCTAssertEqual(action, DispatcherMock.Action.test) 30 | } 31 | 32 | func testDispatcher() { 33 | let translator = TranslatorMock() 34 | let dispatcher = Dispatcher(store: translator.store, translator: translator) 35 | 36 | XCTAssertNil(translator.latestAction) 37 | XCTAssertFalse(translator.store.isCalled) 38 | 39 | dispatcher.dispatch(.test) 40 | 41 | XCTAssertEqual(translator.latestAction, .test) 42 | XCTAssertTrue(translator.store.isCalled) 43 | 44 | translator.latestAction = nil 45 | translator.store.isCalled = false 46 | 47 | dispatcher.asDispatcher().dispatch(.test) 48 | 49 | XCTAssertEqual(translator.latestAction, .test) 50 | XCTAssertTrue(translator.store.isCalled) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /PlanTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PlanTests/InteractorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Plan 3 | 4 | private final class InteractorTests: XCTestCase { 5 | func testInteractorWithDispatchable() { 6 | var action: DispatcherMock.Action? 7 | let dispatcher = DispatcherMock { action = $0 } 8 | 9 | let interactor = Interactor(dispatcher: dispatcher) 10 | interactor.dispatcher.dispatch(.test) 11 | 12 | XCTAssertEqual(action, .test) 13 | } 14 | 15 | func testInteractorAnyDispatcher() { 16 | var action: MockAction? 17 | let dispatcher = AnyDispatcher { action = $0 } 18 | 19 | let interactor = Interactor(dispatcher: dispatcher.asDispatcher()) 20 | interactor.dispatcher.dispatch(.test) 21 | 22 | XCTAssertEqual(action, .test) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PlanTests/Mocks/DispatcherMock.swift: -------------------------------------------------------------------------------- 1 | import Plan 2 | 3 | final class DispatcherMock: Dispatchable { 4 | enum Action { 5 | case test 6 | } 7 | 8 | let _dispatch: (Action) -> Void 9 | 10 | init(action: @escaping (Action) -> Void) { 11 | _dispatch = action 12 | } 13 | 14 | func dispatch(_ action: Action) { 15 | _dispatch(action) 16 | } 17 | 18 | func asDispatcher() -> AnyDispatcher { 19 | AnyDispatcher(self) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PlanTests/Mocks/MockAction.swift: -------------------------------------------------------------------------------- 1 | enum MockAction { 2 | case test 3 | } 4 | -------------------------------------------------------------------------------- /PlanTests/Mocks/TranslatorMock.swift: -------------------------------------------------------------------------------- 1 | import Plan 2 | 3 | final class TranslatorMock: Translator { 4 | enum Action { 5 | case test 6 | } 7 | 8 | final class Store: Plan.Store { 9 | var isCalled = false 10 | } 11 | 12 | let store = Store() 13 | var latestAction: Action? 14 | 15 | func translate(action: Action, store: Store) { 16 | latestAction = action 17 | store.isCalled = true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PlanTests/PresenterTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Plan 3 | 4 | private final class PresenterTests: XCTestCase { 5 | func testPresenter() { 6 | let translator = TranslatorMock() 7 | let presenter = Presenter(store: translator.store, translator: translator) 8 | 9 | presenter.dispatch(.test) 10 | 11 | XCTAssertEqual(translator.latestAction, .test) 12 | XCTAssertTrue(translator.store.isCalled) 13 | 14 | translator.latestAction = nil 15 | translator.store.isCalled = false 16 | 17 | presenter.asDispatcher().dispatch(.test) 18 | 19 | XCTAssertEqual(translator.latestAction, .test) 20 | XCTAssertTrue(translator.store.isCalled) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PlanTests/TranslatorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Plan 3 | 4 | private final class TranslatorTests: XCTestCase { 5 | func testTranslator() { 6 | final class TranslatorMainMock: Translator { 7 | typealias Action = Never 8 | final class Store: Plan.Store {} 9 | var executor: Executor = .main 10 | func translate(action: Action, store: Store) {} 11 | } 12 | 13 | XCTAssertTrue(TranslatorMock().executor.queue is ImmediateQueue) 14 | XCTAssertTrue(TranslatorMainMock().executor.queue is MainThreadQueue) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Support Files/Plan.xcconfig: -------------------------------------------------------------------------------- 1 | SWIFT_VERSION = 5.0 2 | IPHONEOS_DEPLOYMENT_TARGET = 10.0 3 | MACOSX_DEPLOYMENT_TARGET = 10.12 4 | TVOS_DEPLOYMENT_TARGET = 10.0 5 | WATCHOS_DEPLOYMENT_TARGET = 3.0 6 | 7 | SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator watchos watchsimulator appletvos appletvsimulator 8 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "Examples" 3 | - "PlanTests" 4 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app 2 | # apple_id("[[APPLE_ID]]") # Your Apple email address 3 | 4 | 5 | # For more information about the Appfile, see: 6 | # https://docs.fastlane.tools/advanced/#appfile 7 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:ios) 17 | 18 | platform :ios do 19 | desc "Run tests using 'fastlane scan'" 20 | lane :run_project_tests do |options| 21 | scan( 22 | clean: true, 23 | scheme: "Plan", 24 | device: "iPhone 11 Pro", 25 | skip_slack: true, 26 | buildlog_path: "./build/tests_log" 27 | ) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios run_project_tests 20 | ``` 21 | fastlane ios run_project_tests 22 | ``` 23 | Run tests using 'fastlane scan' 24 | ### ios custom_lane 25 | ``` 26 | fastlane ios custom_lane 27 | ``` 28 | Description of what the lane does 29 | 30 | ---- 31 | 32 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 33 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 34 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 35 | --------------------------------------------------------------------------------