├── .gitignore ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── ToDo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── ToDo.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── ToDo ├── App │ ├── AppComponent.swift │ └── AppDelegate.swift ├── Models │ ├── Config.swift │ ├── Item.swift │ └── ItemViewModel.swift ├── RIBs │ ├── ActionableItems │ │ ├── DrawerActionableItem.swift │ │ ├── ListActionableItem.swift │ │ ├── LoggedInActionableItem.swift │ │ └── RootActionableItem.swift │ ├── Common │ │ ├── LoadingPresentable.swift │ │ └── UINavigationController.swift │ ├── Drawer │ │ ├── DrawerBuilder.swift │ │ ├── DrawerComponent+List.swift │ │ ├── DrawerComponent+Menu.swift │ │ ├── DrawerInteractor.swift │ │ ├── DrawerRouter.swift │ │ └── DrawerViewController.swift │ ├── Item │ │ ├── ItemBuilder.swift │ │ ├── ItemInteractor.swift │ │ ├── ItemRouter.swift │ │ ├── ItemViewController.swift │ │ └── ItemViewController.xib │ ├── List │ │ ├── ListBuilder.swift │ │ ├── ListComponent+Item.swift │ │ ├── ListInteractor.swift │ │ ├── ListRouter.swift │ │ ├── ListViewController.swift │ │ └── ListViewController.xib │ ├── LoggedIn │ │ ├── LoggedInBuilder.swift │ │ ├── LoggedInComponent+Drawer.swift │ │ ├── LoggedInInteractor.swift │ │ └── LoggedInRouter.swift │ ├── LoggedOut │ │ ├── LoggedOutBuilder.swift │ │ ├── LoggedOutInteractor.swift │ │ ├── LoggedOutRouter.swift │ │ ├── LoggedOutViewController.swift │ │ └── LoggedOutViewController.xib │ ├── Menu │ │ ├── MenuBuilder.swift │ │ ├── MenuInteractor.swift │ │ ├── MenuRouter.swift │ │ ├── MenuViewController.swift │ │ └── MenuViewController.xib │ ├── Root │ │ ├── RootBuilder.swift │ │ ├── RootComponent+LoggedIn.swift │ │ ├── RootComponent+LoggedOut.swift │ │ ├── RootInteractor.swift │ │ ├── RootRouter.swift │ │ └── RootViewController.swift │ └── Workflows │ │ ├── DeeplinkWorkflow │ │ └── DeeplinkWorkflow.swift │ │ └── RootWorkflow │ │ └── RootWorkflow.swift ├── Resources │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Services │ └── ToDoService.swift ├── Utils │ └── Date.swift └── Views │ ├── ItemCell.swift │ └── ItemCell.xib ├── ToDoTests ├── Info.plist └── ToDoTests.swift └── ToDoUITests ├── Info.plist └── ToDoUITests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dev4Jam 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 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '11.4' 3 | 4 | target 'ToDo' do 5 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 6 | # Pods for ToDo 7 | use_frameworks! 8 | 9 | # Pods for ToDo 10 | pod 'RIBs' 11 | pod 'SnapKit' 12 | pod 'RxCocoa' 13 | pod 'IGListKit' 14 | pod 'TransitionButton'#, :git => "https://github.com/AladinWay/TransitionButton.git" 15 | pod 'SVProgressHUD' 16 | pod 'DynamicButton' 17 | pod 'Realm' 18 | pod 'RealmSwift' 19 | pod 'REFrostedViewController' 20 | pod 'Eureka' 21 | 22 | target 'ToDoTests' do 23 | inherit! :search_paths 24 | # Pods for testing 25 | end 26 | 27 | target 'ToDoUITests' do 28 | inherit! :search_paths 29 | # Pods for testing 30 | end 31 | 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | target.build_configurations.each do |config| 37 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.4' 38 | config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES' 39 | #config.build_settings['ENABLE_BITCODE'] = 'NO' 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DynamicButton (6.2.1) 3 | - Eureka (5.3.3) 4 | - IGListDiffKit (4.0.0) 5 | - IGListKit (4.0.0): 6 | - IGListDiffKit (= 4.0.0) 7 | - Realm (10.7.2): 8 | - Realm/Headers (= 10.7.2) 9 | - Realm/Headers (10.7.2) 10 | - RealmSwift (10.7.2): 11 | - Realm (= 10.7.2) 12 | - REFrostedViewController (2.4.8) 13 | - RIBs (0.9.1): 14 | - RxSwift (~> 4.0) 15 | - RxCocoa (4.5.0): 16 | - RxSwift (>= 4.4.2, ~> 4.4) 17 | - RxSwift (4.5.0) 18 | - SnapKit (5.0.1) 19 | - SVProgressHUD (2.2.5) 20 | - TransitionButton (0.5.3) 21 | 22 | DEPENDENCIES: 23 | - DynamicButton 24 | - Eureka 25 | - IGListKit 26 | - Realm 27 | - RealmSwift 28 | - REFrostedViewController 29 | - RIBs 30 | - RxCocoa 31 | - SnapKit 32 | - SVProgressHUD 33 | - TransitionButton 34 | 35 | SPEC REPOS: 36 | trunk: 37 | - DynamicButton 38 | - Eureka 39 | - IGListDiffKit 40 | - IGListKit 41 | - Realm 42 | - RealmSwift 43 | - REFrostedViewController 44 | - RIBs 45 | - RxCocoa 46 | - RxSwift 47 | - SnapKit 48 | - SVProgressHUD 49 | - TransitionButton 50 | 51 | SPEC CHECKSUMS: 52 | DynamicButton: 95f30460793bd3510a5c213ac1b810a5e3d54b6a 53 | Eureka: e6d720290c56bb23333ad1a0e020b2a98a0e9a43 54 | IGListDiffKit: 665d6cf43ce726e676013db9c7d6c4294259b6b2 55 | IGListKit: fd5a5d21935298f5849fa49d426843cff97b77c7 56 | Realm: e523da9ade306c5ae87e85dc09fdef148d3e1cc1 57 | RealmSwift: 4f6758c3adbdcc87f7b7777107226532a077f61c 58 | REFrostedViewController: a893b4979ba1f200c547aef9ff4c3fd5a18f5923 59 | RIBs: 879184fbca567425c488d6b3d14a9c5abfbf9d47 60 | RxCocoa: cbf70265dc65a981d4ac982e513c10cf23df24a0 61 | RxSwift: f172070dfd1a93d70a9ab97a5a01166206e1c575 62 | SnapKit: 97b92857e3df3a0c71833cce143274bf6ef8e5eb 63 | SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 64 | TransitionButton: 36283346e17c64775a1b3bc3a7a72940a367de1f 65 | 66 | PODFILE CHECKSUM: 82353c97b5e1940f4a62bd7f098391a76d858f4b 67 | 68 | COCOAPODS: 1.10.1 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Platform: iOS 11+ 3 | Language: Swift 4.0 4 | License: MIT 5 | 6 |

7 | 8 | # ToDo 9 | 10 | ToDo iOS app is a demo of RIBs architecture https://github.com/uber/RIBs/wiki 11 | 12 | # You can find examples of: 13 | 14 | 0. RIBs architecture in real application 15 | 1. Presenting / Dismissing modal view controller 16 | 2. Pushing / Poping a view controller 17 | 3. Handling back gesture and back button tap in UINavigationController 18 | 4. Working with system view controllers (UIAlertController and others) 19 | 5. Side/drawer menu 20 | 6. Handling deep links 21 | 7. Building workflows 22 | 8. Scopes 23 | 24 | -------------------------------------------------------------------------------- /ToDo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 054BB18E6B3628A601DCE50B /* Pods_ToDoTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4E4874F7ED8C2FBCB7BB86E /* Pods_ToDoTests.framework */; }; 11 | 0A0CF40820231EF20010892B /* LoggedInActionableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF40020231EF20010892B /* LoggedInActionableItem.swift */; }; 12 | 0A0CF40920231EF20010892B /* DrawerActionableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF40120231EF20010892B /* DrawerActionableItem.swift */; }; 13 | 0A0CF40A20231EF20010892B /* RootActionableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF40220231EF20010892B /* RootActionableItem.swift */; }; 14 | 0A0CF40B20231EF20010892B /* RootWorkflow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF40520231EF20010892B /* RootWorkflow.swift */; }; 15 | 0A0CF40C20231EF20010892B /* DeeplinkWorkflow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF40720231EF20010892B /* DeeplinkWorkflow.swift */; }; 16 | 0A0CF40E20231FA60010892B /* UINavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF40D20231FA60010892B /* UINavigationController.swift */; }; 17 | 0A0CF410202321530010892B /* LoggedInComponent+Drawer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF40F202321530010892B /* LoggedInComponent+Drawer.swift */; }; 18 | 0A0CF412202324460010892B /* DrawerComponent+List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF411202324460010892B /* DrawerComponent+List.swift */; }; 19 | 0A0CF4192023D0940010892B /* MenuRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF4142023D0940010892B /* MenuRouter.swift */; }; 20 | 0A0CF41A2023D0940010892B /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF4152023D0940010892B /* MenuViewController.swift */; }; 21 | 0A0CF41B2023D0940010892B /* MenuBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF4162023D0940010892B /* MenuBuilder.swift */; }; 22 | 0A0CF41C2023D0940010892B /* MenuInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF4172023D0940010892B /* MenuInteractor.swift */; }; 23 | 0A0CF41D2023D0940010892B /* MenuViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0A0CF4182023D0940010892B /* MenuViewController.xib */; }; 24 | 0A0CF41F2023D0B50010892B /* DrawerComponent+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF41E2023D0B50010892B /* DrawerComponent+Menu.swift */; }; 25 | 0A0CF4212023D45B0010892B /* ListViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0A0CF4202023D45B0010892B /* ListViewController.xib */; }; 26 | 0A0CF4232023DDCD0010892B /* ListActionableItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A0CF4222023DDCD0010892B /* ListActionableItem.swift */; }; 27 | 0A398A3D20229EBE00741F6D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A3C20229EBE00741F6D /* AppDelegate.swift */; }; 28 | 0A398A4420229EBE00741F6D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A398A4320229EBE00741F6D /* Assets.xcassets */; }; 29 | 0A398A4720229EBE00741F6D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0A398A4520229EBE00741F6D /* LaunchScreen.storyboard */; }; 30 | 0A398A5220229EBF00741F6D /* ToDoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A5120229EBF00741F6D /* ToDoTests.swift */; }; 31 | 0A398A5D20229EBF00741F6D /* ToDoUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A5C20229EBF00741F6D /* ToDoUITests.swift */; }; 32 | 0A398A6D2022A15F00741F6D /* AppComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A6C2022A15F00741F6D /* AppComponent.swift */; }; 33 | 0A398A742022A1B700741F6D /* ListRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A702022A1B700741F6D /* ListRouter.swift */; }; 34 | 0A398A752022A1B700741F6D /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A712022A1B700741F6D /* ListViewController.swift */; }; 35 | 0A398A762022A1B700741F6D /* ListBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A722022A1B700741F6D /* ListBuilder.swift */; }; 36 | 0A398A772022A1B700741F6D /* ListInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A732022A1B700741F6D /* ListInteractor.swift */; }; 37 | 0A398A7A2022A39D00741F6D /* Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A792022A39D00741F6D /* Item.swift */; }; 38 | 0A398A7D2022A45400741F6D /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A7C2022A45400741F6D /* Date.swift */; }; 39 | 0A398A802022A4CD00741F6D /* ToDoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A7F2022A4CD00741F6D /* ToDoService.swift */; }; 40 | 0A398A842022A75400741F6D /* LoadingPresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A832022A75400741F6D /* LoadingPresentable.swift */; }; 41 | 0A398A862022A88A00741F6D /* ItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A852022A88A00741F6D /* ItemViewModel.swift */; }; 42 | 0A398A8C2022AD2D00741F6D /* ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A8A2022AD2D00741F6D /* ItemCell.swift */; }; 43 | 0A398A8D2022AD2D00741F6D /* ItemCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0A398A8B2022AD2D00741F6D /* ItemCell.xib */; }; 44 | 0A398A972022C87A00741F6D /* ItemRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A922022C87A00741F6D /* ItemRouter.swift */; }; 45 | 0A398A982022C87A00741F6D /* ItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A932022C87A00741F6D /* ItemViewController.swift */; }; 46 | 0A398A992022C87A00741F6D /* ItemBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A942022C87A00741F6D /* ItemBuilder.swift */; }; 47 | 0A398A9A2022C87A00741F6D /* ItemInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A952022C87A00741F6D /* ItemInteractor.swift */; }; 48 | 0A398A9B2022C87A00741F6D /* ItemViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0A398A962022C87A00741F6D /* ItemViewController.xib */; }; 49 | 0A398A9D2022D7DD00741F6D /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A9C2022D7DD00741F6D /* Config.swift */; }; 50 | 0A398A9F2022D9A200741F6D /* ListComponent+Item.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398A9E2022D9A200741F6D /* ListComponent+Item.swift */; }; 51 | 0A398AA52023139200741F6D /* RootRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AA12023139200741F6D /* RootRouter.swift */; }; 52 | 0A398AA62023139200741F6D /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AA22023139200741F6D /* RootViewController.swift */; }; 53 | 0A398AA72023139200741F6D /* RootBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AA32023139200741F6D /* RootBuilder.swift */; }; 54 | 0A398AA82023139200741F6D /* RootInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AA42023139200741F6D /* RootInteractor.swift */; }; 55 | 0A398AAB2023182800741F6D /* RootComponent+LoggedOut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AA92023182800741F6D /* RootComponent+LoggedOut.swift */; }; 56 | 0A398AAC2023182800741F6D /* RootComponent+LoggedIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AAA2023182800741F6D /* RootComponent+LoggedIn.swift */; }; 57 | 0A398ABB2023188200741F6D /* LoggedOutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AAE2023188200741F6D /* LoggedOutViewController.swift */; }; 58 | 0A398ABC2023188200741F6D /* LoggedOutViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0A398AAF2023188200741F6D /* LoggedOutViewController.xib */; }; 59 | 0A398ABD2023188200741F6D /* LoggedOutInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AB02023188200741F6D /* LoggedOutInteractor.swift */; }; 60 | 0A398ABE2023188200741F6D /* LoggedOutBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AB12023188200741F6D /* LoggedOutBuilder.swift */; }; 61 | 0A398ABF2023188200741F6D /* LoggedOutRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AB22023188200741F6D /* LoggedOutRouter.swift */; }; 62 | 0A398AC22023188200741F6D /* LoggedInBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AB62023188200741F6D /* LoggedInBuilder.swift */; }; 63 | 0A398AC32023188200741F6D /* LoggedInRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AB72023188200741F6D /* LoggedInRouter.swift */; }; 64 | 0A398AC42023188200741F6D /* LoggedInInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AB82023188200741F6D /* LoggedInInteractor.swift */; }; 65 | 0A398ACC20231A6900741F6D /* DrawerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AC820231A6900741F6D /* DrawerViewController.swift */; }; 66 | 0A398ACD20231A6900741F6D /* DrawerInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398AC920231A6900741F6D /* DrawerInteractor.swift */; }; 67 | 0A398ACE20231A6900741F6D /* DrawerRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398ACA20231A6900741F6D /* DrawerRouter.swift */; }; 68 | 0A398ACF20231A6900741F6D /* DrawerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A398ACB20231A6900741F6D /* DrawerBuilder.swift */; }; 69 | 0A4DA10C6375B6A71BAA5334 /* Pods_ToDoUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6C62C235366A3AA92388A477 /* Pods_ToDoUITests.framework */; }; 70 | 53F42407639FAB86D89ECAD5 /* Pods_ToDo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 916A48A5C900CAD5E6DBE4EA /* Pods_ToDo.framework */; }; 71 | /* End PBXBuildFile section */ 72 | 73 | /* Begin PBXContainerItemProxy section */ 74 | 0A398A4E20229EBF00741F6D /* PBXContainerItemProxy */ = { 75 | isa = PBXContainerItemProxy; 76 | containerPortal = 0A398A3120229EBE00741F6D /* Project object */; 77 | proxyType = 1; 78 | remoteGlobalIDString = 0A398A3820229EBE00741F6D; 79 | remoteInfo = ToDo; 80 | }; 81 | 0A398A5920229EBF00741F6D /* PBXContainerItemProxy */ = { 82 | isa = PBXContainerItemProxy; 83 | containerPortal = 0A398A3120229EBE00741F6D /* Project object */; 84 | proxyType = 1; 85 | remoteGlobalIDString = 0A398A3820229EBE00741F6D; 86 | remoteInfo = ToDo; 87 | }; 88 | /* End PBXContainerItemProxy section */ 89 | 90 | /* Begin PBXFileReference section */ 91 | 03605E0BDA10357A4A3F9F4D /* Pods-ToDo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ToDo.release.xcconfig"; path = "Pods/Target Support Files/Pods-ToDo/Pods-ToDo.release.xcconfig"; sourceTree = ""; }; 92 | 0A0CF40020231EF20010892B /* LoggedInActionableItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInActionableItem.swift; sourceTree = ""; }; 93 | 0A0CF40120231EF20010892B /* DrawerActionableItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerActionableItem.swift; sourceTree = ""; }; 94 | 0A0CF40220231EF20010892B /* RootActionableItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootActionableItem.swift; sourceTree = ""; }; 95 | 0A0CF40520231EF20010892B /* RootWorkflow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootWorkflow.swift; sourceTree = ""; }; 96 | 0A0CF40720231EF20010892B /* DeeplinkWorkflow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeeplinkWorkflow.swift; sourceTree = ""; }; 97 | 0A0CF40D20231FA60010892B /* UINavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UINavigationController.swift; sourceTree = ""; }; 98 | 0A0CF40F202321530010892B /* LoggedInComponent+Drawer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "LoggedInComponent+Drawer.swift"; sourceTree = ""; }; 99 | 0A0CF411202324460010892B /* DrawerComponent+List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerComponent+List.swift"; sourceTree = ""; }; 100 | 0A0CF4142023D0940010892B /* MenuRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuRouter.swift; sourceTree = ""; }; 101 | 0A0CF4152023D0940010892B /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = ""; }; 102 | 0A0CF4162023D0940010892B /* MenuBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBuilder.swift; sourceTree = ""; }; 103 | 0A0CF4172023D0940010892B /* MenuInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuInteractor.swift; sourceTree = ""; }; 104 | 0A0CF4182023D0940010892B /* MenuViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MenuViewController.xib; sourceTree = ""; }; 105 | 0A0CF41E2023D0B50010892B /* DrawerComponent+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DrawerComponent+Menu.swift"; sourceTree = ""; }; 106 | 0A0CF4202023D45B0010892B /* ListViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ListViewController.xib; sourceTree = ""; }; 107 | 0A0CF4222023DDCD0010892B /* ListActionableItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListActionableItem.swift; sourceTree = ""; }; 108 | 0A398A3920229EBE00741F6D /* ToDo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ToDo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 109 | 0A398A3C20229EBE00741F6D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 110 | 0A398A4320229EBE00741F6D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 111 | 0A398A4620229EBE00741F6D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 112 | 0A398A4820229EBF00741F6D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 113 | 0A398A4D20229EBF00741F6D /* ToDoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ToDoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 114 | 0A398A5120229EBF00741F6D /* ToDoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoTests.swift; sourceTree = ""; }; 115 | 0A398A5320229EBF00741F6D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 116 | 0A398A5820229EBF00741F6D /* ToDoUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ToDoUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 117 | 0A398A5C20229EBF00741F6D /* ToDoUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoUITests.swift; sourceTree = ""; }; 118 | 0A398A5E20229EBF00741F6D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 119 | 0A398A6C2022A15F00741F6D /* AppComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppComponent.swift; sourceTree = ""; }; 120 | 0A398A702022A1B700741F6D /* ListRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRouter.swift; sourceTree = ""; }; 121 | 0A398A712022A1B700741F6D /* ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewController.swift; sourceTree = ""; }; 122 | 0A398A722022A1B700741F6D /* ListBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListBuilder.swift; sourceTree = ""; }; 123 | 0A398A732022A1B700741F6D /* ListInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListInteractor.swift; sourceTree = ""; }; 124 | 0A398A792022A39D00741F6D /* Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Item.swift; sourceTree = ""; }; 125 | 0A398A7C2022A45400741F6D /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 126 | 0A398A7F2022A4CD00741F6D /* ToDoService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToDoService.swift; sourceTree = ""; }; 127 | 0A398A832022A75400741F6D /* LoadingPresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingPresentable.swift; sourceTree = ""; }; 128 | 0A398A852022A88A00741F6D /* ItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewModel.swift; sourceTree = ""; }; 129 | 0A398A8A2022AD2D00741F6D /* ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCell.swift; sourceTree = ""; }; 130 | 0A398A8B2022AD2D00741F6D /* ItemCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ItemCell.xib; sourceTree = ""; }; 131 | 0A398A922022C87A00741F6D /* ItemRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemRouter.swift; sourceTree = ""; }; 132 | 0A398A932022C87A00741F6D /* ItemViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViewController.swift; sourceTree = ""; }; 133 | 0A398A942022C87A00741F6D /* ItemBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemBuilder.swift; sourceTree = ""; }; 134 | 0A398A952022C87A00741F6D /* ItemInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemInteractor.swift; sourceTree = ""; }; 135 | 0A398A962022C87A00741F6D /* ItemViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ItemViewController.xib; sourceTree = ""; }; 136 | 0A398A9C2022D7DD00741F6D /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 137 | 0A398A9E2022D9A200741F6D /* ListComponent+Item.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListComponent+Item.swift"; sourceTree = ""; }; 138 | 0A398AA12023139200741F6D /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = ""; }; 139 | 0A398AA22023139200741F6D /* RootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; 140 | 0A398AA32023139200741F6D /* RootBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootBuilder.swift; sourceTree = ""; }; 141 | 0A398AA42023139200741F6D /* RootInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootInteractor.swift; sourceTree = ""; }; 142 | 0A398AA92023182800741F6D /* RootComponent+LoggedOut.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RootComponent+LoggedOut.swift"; sourceTree = ""; }; 143 | 0A398AAA2023182800741F6D /* RootComponent+LoggedIn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RootComponent+LoggedIn.swift"; sourceTree = ""; }; 144 | 0A398AAE2023188200741F6D /* LoggedOutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutViewController.swift; sourceTree = ""; }; 145 | 0A398AAF2023188200741F6D /* LoggedOutViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LoggedOutViewController.xib; sourceTree = ""; }; 146 | 0A398AB02023188200741F6D /* LoggedOutInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutInteractor.swift; sourceTree = ""; }; 147 | 0A398AB12023188200741F6D /* LoggedOutBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutBuilder.swift; sourceTree = ""; }; 148 | 0A398AB22023188200741F6D /* LoggedOutRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedOutRouter.swift; sourceTree = ""; }; 149 | 0A398AB62023188200741F6D /* LoggedInBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInBuilder.swift; sourceTree = ""; }; 150 | 0A398AB72023188200741F6D /* LoggedInRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInRouter.swift; sourceTree = ""; }; 151 | 0A398AB82023188200741F6D /* LoggedInInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoggedInInteractor.swift; sourceTree = ""; }; 152 | 0A398AC820231A6900741F6D /* DrawerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerViewController.swift; sourceTree = ""; }; 153 | 0A398AC920231A6900741F6D /* DrawerInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerInteractor.swift; sourceTree = ""; }; 154 | 0A398ACA20231A6900741F6D /* DrawerRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerRouter.swift; sourceTree = ""; }; 155 | 0A398ACB20231A6900741F6D /* DrawerBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerBuilder.swift; sourceTree = ""; }; 156 | 100052882F75295A287FAA47 /* Pods-ToDoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ToDoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ToDoTests/Pods-ToDoTests.debug.xcconfig"; sourceTree = ""; }; 157 | 33B0C1192983C4F2C0FD30CF /* Pods-ToDoUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ToDoUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ToDoUITests/Pods-ToDoUITests.debug.xcconfig"; sourceTree = ""; }; 158 | 4671FC4DBBB94037D17F2066 /* Pods-ToDoUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ToDoUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ToDoUITests/Pods-ToDoUITests.release.xcconfig"; sourceTree = ""; }; 159 | 6C62C235366A3AA92388A477 /* Pods_ToDoUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ToDoUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 160 | 916A48A5C900CAD5E6DBE4EA /* Pods_ToDo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ToDo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 161 | C4E4874F7ED8C2FBCB7BB86E /* Pods_ToDoTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ToDoTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 162 | CE013D3E86FEFF3D06480E67 /* Pods-ToDo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ToDo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ToDo/Pods-ToDo.debug.xcconfig"; sourceTree = ""; }; 163 | E9FA3507A60CD3E496C7878A /* Pods-ToDoTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ToDoTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ToDoTests/Pods-ToDoTests.release.xcconfig"; sourceTree = ""; }; 164 | /* End PBXFileReference section */ 165 | 166 | /* Begin PBXFrameworksBuildPhase section */ 167 | 0A398A3620229EBE00741F6D /* Frameworks */ = { 168 | isa = PBXFrameworksBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | 53F42407639FAB86D89ECAD5 /* Pods_ToDo.framework in Frameworks */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | 0A398A4A20229EBF00741F6D /* Frameworks */ = { 176 | isa = PBXFrameworksBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | 054BB18E6B3628A601DCE50B /* Pods_ToDoTests.framework in Frameworks */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | 0A398A5520229EBF00741F6D /* Frameworks */ = { 184 | isa = PBXFrameworksBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 0A4DA10C6375B6A71BAA5334 /* Pods_ToDoUITests.framework in Frameworks */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXFrameworksBuildPhase section */ 192 | 193 | /* Begin PBXGroup section */ 194 | 0A0CF3FF20231EF20010892B /* ActionableItems */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 0A0CF40020231EF20010892B /* LoggedInActionableItem.swift */, 198 | 0A0CF40120231EF20010892B /* DrawerActionableItem.swift */, 199 | 0A0CF4222023DDCD0010892B /* ListActionableItem.swift */, 200 | 0A0CF40220231EF20010892B /* RootActionableItem.swift */, 201 | ); 202 | path = ActionableItems; 203 | sourceTree = ""; 204 | }; 205 | 0A0CF40320231EF20010892B /* Workflows */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 0A0CF40420231EF20010892B /* RootWorkflow */, 209 | 0A0CF40620231EF20010892B /* DeeplinkWorkflow */, 210 | ); 211 | path = Workflows; 212 | sourceTree = ""; 213 | }; 214 | 0A0CF40420231EF20010892B /* RootWorkflow */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | 0A0CF40520231EF20010892B /* RootWorkflow.swift */, 218 | ); 219 | path = RootWorkflow; 220 | sourceTree = ""; 221 | }; 222 | 0A0CF40620231EF20010892B /* DeeplinkWorkflow */ = { 223 | isa = PBXGroup; 224 | children = ( 225 | 0A0CF40720231EF20010892B /* DeeplinkWorkflow.swift */, 226 | ); 227 | path = DeeplinkWorkflow; 228 | sourceTree = ""; 229 | }; 230 | 0A0CF4132023D0860010892B /* Menu */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | 0A0CF4162023D0940010892B /* MenuBuilder.swift */, 234 | 0A0CF4142023D0940010892B /* MenuRouter.swift */, 235 | 0A0CF4172023D0940010892B /* MenuInteractor.swift */, 236 | 0A0CF4152023D0940010892B /* MenuViewController.swift */, 237 | 0A0CF4182023D0940010892B /* MenuViewController.xib */, 238 | ); 239 | path = Menu; 240 | sourceTree = ""; 241 | }; 242 | 0A398A3020229EBE00741F6D = { 243 | isa = PBXGroup; 244 | children = ( 245 | 0A398A3B20229EBE00741F6D /* ToDo */, 246 | 0A398A5020229EBF00741F6D /* ToDoTests */, 247 | 0A398A5B20229EBF00741F6D /* ToDoUITests */, 248 | 0A398A3A20229EBE00741F6D /* Products */, 249 | 18A4E32003849CFD79BE49E0 /* Pods */, 250 | B5046D93DB3F818116951289 /* Frameworks */, 251 | ); 252 | sourceTree = ""; 253 | }; 254 | 0A398A3A20229EBE00741F6D /* Products */ = { 255 | isa = PBXGroup; 256 | children = ( 257 | 0A398A3920229EBE00741F6D /* ToDo.app */, 258 | 0A398A4D20229EBF00741F6D /* ToDoTests.xctest */, 259 | 0A398A5820229EBF00741F6D /* ToDoUITests.xctest */, 260 | ); 261 | name = Products; 262 | sourceTree = ""; 263 | }; 264 | 0A398A3B20229EBE00741F6D /* ToDo */ = { 265 | isa = PBXGroup; 266 | children = ( 267 | 0A398A6B20229EDD00741F6D /* App */, 268 | 0A398A6E2022A18100741F6D /* RIBs */, 269 | 0A398A892022AD0E00741F6D /* Views */, 270 | 0A398A782022A38B00741F6D /* Models */, 271 | 0A398A7E2022A4B100741F6D /* Services */, 272 | 0A398A7B2022A44500741F6D /* Utils */, 273 | 0A398A6A20229EC800741F6D /* Resources */, 274 | ); 275 | path = ToDo; 276 | sourceTree = ""; 277 | }; 278 | 0A398A5020229EBF00741F6D /* ToDoTests */ = { 279 | isa = PBXGroup; 280 | children = ( 281 | 0A398A5120229EBF00741F6D /* ToDoTests.swift */, 282 | 0A398A5320229EBF00741F6D /* Info.plist */, 283 | ); 284 | path = ToDoTests; 285 | sourceTree = ""; 286 | }; 287 | 0A398A5B20229EBF00741F6D /* ToDoUITests */ = { 288 | isa = PBXGroup; 289 | children = ( 290 | 0A398A5C20229EBF00741F6D /* ToDoUITests.swift */, 291 | 0A398A5E20229EBF00741F6D /* Info.plist */, 292 | ); 293 | path = ToDoUITests; 294 | sourceTree = ""; 295 | }; 296 | 0A398A6A20229EC800741F6D /* Resources */ = { 297 | isa = PBXGroup; 298 | children = ( 299 | 0A398A4320229EBE00741F6D /* Assets.xcassets */, 300 | 0A398A4520229EBE00741F6D /* LaunchScreen.storyboard */, 301 | 0A398A4820229EBF00741F6D /* Info.plist */, 302 | ); 303 | path = Resources; 304 | sourceTree = ""; 305 | }; 306 | 0A398A6B20229EDD00741F6D /* App */ = { 307 | isa = PBXGroup; 308 | children = ( 309 | 0A398A3C20229EBE00741F6D /* AppDelegate.swift */, 310 | 0A398A6C2022A15F00741F6D /* AppComponent.swift */, 311 | ); 312 | path = App; 313 | sourceTree = ""; 314 | }; 315 | 0A398A6E2022A18100741F6D /* RIBs */ = { 316 | isa = PBXGroup; 317 | children = ( 318 | 0A398A822022A74300741F6D /* Common */, 319 | 0A398AA02023137100741F6D /* Root */, 320 | 0A398AB32023188200741F6D /* LoggedIn */, 321 | 0A398AAD2023188200741F6D /* LoggedOut */, 322 | 0A398AC720231A6900741F6D /* Drawer */, 323 | 0A0CF4132023D0860010892B /* Menu */, 324 | 0A398A6F2022A18900741F6D /* List */, 325 | 0A398A912022C86600741F6D /* Item */, 326 | 0A0CF3FF20231EF20010892B /* ActionableItems */, 327 | 0A0CF40320231EF20010892B /* Workflows */, 328 | ); 329 | path = RIBs; 330 | sourceTree = ""; 331 | }; 332 | 0A398A6F2022A18900741F6D /* List */ = { 333 | isa = PBXGroup; 334 | children = ( 335 | 0A398A722022A1B700741F6D /* ListBuilder.swift */, 336 | 0A398A702022A1B700741F6D /* ListRouter.swift */, 337 | 0A398A732022A1B700741F6D /* ListInteractor.swift */, 338 | 0A398A712022A1B700741F6D /* ListViewController.swift */, 339 | 0A0CF4202023D45B0010892B /* ListViewController.xib */, 340 | 0A398A9E2022D9A200741F6D /* ListComponent+Item.swift */, 341 | ); 342 | path = List; 343 | sourceTree = ""; 344 | }; 345 | 0A398A782022A38B00741F6D /* Models */ = { 346 | isa = PBXGroup; 347 | children = ( 348 | 0A398A792022A39D00741F6D /* Item.swift */, 349 | 0A398A852022A88A00741F6D /* ItemViewModel.swift */, 350 | 0A398A9C2022D7DD00741F6D /* Config.swift */, 351 | ); 352 | path = Models; 353 | sourceTree = ""; 354 | }; 355 | 0A398A7B2022A44500741F6D /* Utils */ = { 356 | isa = PBXGroup; 357 | children = ( 358 | 0A398A7C2022A45400741F6D /* Date.swift */, 359 | ); 360 | path = Utils; 361 | sourceTree = ""; 362 | }; 363 | 0A398A7E2022A4B100741F6D /* Services */ = { 364 | isa = PBXGroup; 365 | children = ( 366 | 0A398A7F2022A4CD00741F6D /* ToDoService.swift */, 367 | ); 368 | path = Services; 369 | sourceTree = ""; 370 | }; 371 | 0A398A822022A74300741F6D /* Common */ = { 372 | isa = PBXGroup; 373 | children = ( 374 | 0A398A832022A75400741F6D /* LoadingPresentable.swift */, 375 | 0A0CF40D20231FA60010892B /* UINavigationController.swift */, 376 | ); 377 | path = Common; 378 | sourceTree = ""; 379 | }; 380 | 0A398A892022AD0E00741F6D /* Views */ = { 381 | isa = PBXGroup; 382 | children = ( 383 | 0A398A8A2022AD2D00741F6D /* ItemCell.swift */, 384 | 0A398A8B2022AD2D00741F6D /* ItemCell.xib */, 385 | ); 386 | path = Views; 387 | sourceTree = ""; 388 | }; 389 | 0A398A912022C86600741F6D /* Item */ = { 390 | isa = PBXGroup; 391 | children = ( 392 | 0A398A942022C87A00741F6D /* ItemBuilder.swift */, 393 | 0A398A922022C87A00741F6D /* ItemRouter.swift */, 394 | 0A398A952022C87A00741F6D /* ItemInteractor.swift */, 395 | 0A398A932022C87A00741F6D /* ItemViewController.swift */, 396 | 0A398A962022C87A00741F6D /* ItemViewController.xib */, 397 | ); 398 | path = Item; 399 | sourceTree = ""; 400 | }; 401 | 0A398AA02023137100741F6D /* Root */ = { 402 | isa = PBXGroup; 403 | children = ( 404 | 0A398AA32023139200741F6D /* RootBuilder.swift */, 405 | 0A398AA12023139200741F6D /* RootRouter.swift */, 406 | 0A398AA42023139200741F6D /* RootInteractor.swift */, 407 | 0A398AA22023139200741F6D /* RootViewController.swift */, 408 | 0A398AAA2023182800741F6D /* RootComponent+LoggedIn.swift */, 409 | 0A398AA92023182800741F6D /* RootComponent+LoggedOut.swift */, 410 | ); 411 | path = Root; 412 | sourceTree = ""; 413 | }; 414 | 0A398AAD2023188200741F6D /* LoggedOut */ = { 415 | isa = PBXGroup; 416 | children = ( 417 | 0A398AB12023188200741F6D /* LoggedOutBuilder.swift */, 418 | 0A398AB22023188200741F6D /* LoggedOutRouter.swift */, 419 | 0A398AB02023188200741F6D /* LoggedOutInteractor.swift */, 420 | 0A398AAE2023188200741F6D /* LoggedOutViewController.swift */, 421 | 0A398AAF2023188200741F6D /* LoggedOutViewController.xib */, 422 | ); 423 | path = LoggedOut; 424 | sourceTree = ""; 425 | }; 426 | 0A398AB32023188200741F6D /* LoggedIn */ = { 427 | isa = PBXGroup; 428 | children = ( 429 | 0A398AB62023188200741F6D /* LoggedInBuilder.swift */, 430 | 0A398AB72023188200741F6D /* LoggedInRouter.swift */, 431 | 0A398AB82023188200741F6D /* LoggedInInteractor.swift */, 432 | 0A0CF40F202321530010892B /* LoggedInComponent+Drawer.swift */, 433 | ); 434 | path = LoggedIn; 435 | sourceTree = ""; 436 | }; 437 | 0A398AC720231A6900741F6D /* Drawer */ = { 438 | isa = PBXGroup; 439 | children = ( 440 | 0A398ACB20231A6900741F6D /* DrawerBuilder.swift */, 441 | 0A398ACA20231A6900741F6D /* DrawerRouter.swift */, 442 | 0A398AC920231A6900741F6D /* DrawerInteractor.swift */, 443 | 0A398AC820231A6900741F6D /* DrawerViewController.swift */, 444 | 0A0CF411202324460010892B /* DrawerComponent+List.swift */, 445 | 0A0CF41E2023D0B50010892B /* DrawerComponent+Menu.swift */, 446 | ); 447 | path = Drawer; 448 | sourceTree = ""; 449 | }; 450 | 18A4E32003849CFD79BE49E0 /* Pods */ = { 451 | isa = PBXGroup; 452 | children = ( 453 | CE013D3E86FEFF3D06480E67 /* Pods-ToDo.debug.xcconfig */, 454 | 03605E0BDA10357A4A3F9F4D /* Pods-ToDo.release.xcconfig */, 455 | 100052882F75295A287FAA47 /* Pods-ToDoTests.debug.xcconfig */, 456 | E9FA3507A60CD3E496C7878A /* Pods-ToDoTests.release.xcconfig */, 457 | 33B0C1192983C4F2C0FD30CF /* Pods-ToDoUITests.debug.xcconfig */, 458 | 4671FC4DBBB94037D17F2066 /* Pods-ToDoUITests.release.xcconfig */, 459 | ); 460 | name = Pods; 461 | sourceTree = ""; 462 | }; 463 | B5046D93DB3F818116951289 /* Frameworks */ = { 464 | isa = PBXGroup; 465 | children = ( 466 | 916A48A5C900CAD5E6DBE4EA /* Pods_ToDo.framework */, 467 | C4E4874F7ED8C2FBCB7BB86E /* Pods_ToDoTests.framework */, 468 | 6C62C235366A3AA92388A477 /* Pods_ToDoUITests.framework */, 469 | ); 470 | name = Frameworks; 471 | sourceTree = ""; 472 | }; 473 | /* End PBXGroup section */ 474 | 475 | /* Begin PBXNativeTarget section */ 476 | 0A398A3820229EBE00741F6D /* ToDo */ = { 477 | isa = PBXNativeTarget; 478 | buildConfigurationList = 0A398A6120229EBF00741F6D /* Build configuration list for PBXNativeTarget "ToDo" */; 479 | buildPhases = ( 480 | 757A4E7E56EA4FB0AA9CC45A /* [CP] Check Pods Manifest.lock */, 481 | 0A398A3520229EBE00741F6D /* Sources */, 482 | 0A398A3620229EBE00741F6D /* Frameworks */, 483 | 0A398A3720229EBE00741F6D /* Resources */, 484 | 44F2F90FCE6F87CC29EA0CC4 /* [CP] Embed Pods Frameworks */, 485 | ); 486 | buildRules = ( 487 | ); 488 | dependencies = ( 489 | ); 490 | name = ToDo; 491 | productName = ToDo; 492 | productReference = 0A398A3920229EBE00741F6D /* ToDo.app */; 493 | productType = "com.apple.product-type.application"; 494 | }; 495 | 0A398A4C20229EBF00741F6D /* ToDoTests */ = { 496 | isa = PBXNativeTarget; 497 | buildConfigurationList = 0A398A6420229EBF00741F6D /* Build configuration list for PBXNativeTarget "ToDoTests" */; 498 | buildPhases = ( 499 | E1602153A3D8468ED61169AB /* [CP] Check Pods Manifest.lock */, 500 | 0A398A4920229EBF00741F6D /* Sources */, 501 | 0A398A4A20229EBF00741F6D /* Frameworks */, 502 | 0A398A4B20229EBF00741F6D /* Resources */, 503 | ); 504 | buildRules = ( 505 | ); 506 | dependencies = ( 507 | 0A398A4F20229EBF00741F6D /* PBXTargetDependency */, 508 | ); 509 | name = ToDoTests; 510 | productName = ToDoTests; 511 | productReference = 0A398A4D20229EBF00741F6D /* ToDoTests.xctest */; 512 | productType = "com.apple.product-type.bundle.unit-test"; 513 | }; 514 | 0A398A5720229EBF00741F6D /* ToDoUITests */ = { 515 | isa = PBXNativeTarget; 516 | buildConfigurationList = 0A398A6720229EBF00741F6D /* Build configuration list for PBXNativeTarget "ToDoUITests" */; 517 | buildPhases = ( 518 | 39261D78BCE753E58A5015E2 /* [CP] Check Pods Manifest.lock */, 519 | 0A398A5420229EBF00741F6D /* Sources */, 520 | 0A398A5520229EBF00741F6D /* Frameworks */, 521 | 0A398A5620229EBF00741F6D /* Resources */, 522 | ); 523 | buildRules = ( 524 | ); 525 | dependencies = ( 526 | 0A398A5A20229EBF00741F6D /* PBXTargetDependency */, 527 | ); 528 | name = ToDoUITests; 529 | productName = ToDoUITests; 530 | productReference = 0A398A5820229EBF00741F6D /* ToDoUITests.xctest */; 531 | productType = "com.apple.product-type.bundle.ui-testing"; 532 | }; 533 | /* End PBXNativeTarget section */ 534 | 535 | /* Begin PBXProject section */ 536 | 0A398A3120229EBE00741F6D /* Project object */ = { 537 | isa = PBXProject; 538 | attributes = { 539 | LastSwiftUpdateCheck = 0920; 540 | LastUpgradeCheck = 1010; 541 | ORGANIZATIONNAME = Dev4Jam; 542 | TargetAttributes = { 543 | 0A398A3820229EBE00741F6D = { 544 | CreatedOnToolsVersion = 9.2; 545 | ProvisioningStyle = Automatic; 546 | }; 547 | 0A398A4C20229EBF00741F6D = { 548 | CreatedOnToolsVersion = 9.2; 549 | LastSwiftMigration = 1010; 550 | ProvisioningStyle = Automatic; 551 | TestTargetID = 0A398A3820229EBE00741F6D; 552 | }; 553 | 0A398A5720229EBF00741F6D = { 554 | CreatedOnToolsVersion = 9.2; 555 | LastSwiftMigration = 1010; 556 | ProvisioningStyle = Automatic; 557 | TestTargetID = 0A398A3820229EBE00741F6D; 558 | }; 559 | }; 560 | }; 561 | buildConfigurationList = 0A398A3420229EBE00741F6D /* Build configuration list for PBXProject "ToDo" */; 562 | compatibilityVersion = "Xcode 8.0"; 563 | developmentRegion = en; 564 | hasScannedForEncodings = 0; 565 | knownRegions = ( 566 | en, 567 | Base, 568 | ); 569 | mainGroup = 0A398A3020229EBE00741F6D; 570 | productRefGroup = 0A398A3A20229EBE00741F6D /* Products */; 571 | projectDirPath = ""; 572 | projectRoot = ""; 573 | targets = ( 574 | 0A398A3820229EBE00741F6D /* ToDo */, 575 | 0A398A4C20229EBF00741F6D /* ToDoTests */, 576 | 0A398A5720229EBF00741F6D /* ToDoUITests */, 577 | ); 578 | }; 579 | /* End PBXProject section */ 580 | 581 | /* Begin PBXResourcesBuildPhase section */ 582 | 0A398A3720229EBE00741F6D /* Resources */ = { 583 | isa = PBXResourcesBuildPhase; 584 | buildActionMask = 2147483647; 585 | files = ( 586 | 0A398ABC2023188200741F6D /* LoggedOutViewController.xib in Resources */, 587 | 0A0CF41D2023D0940010892B /* MenuViewController.xib in Resources */, 588 | 0A0CF4212023D45B0010892B /* ListViewController.xib in Resources */, 589 | 0A398A4720229EBE00741F6D /* LaunchScreen.storyboard in Resources */, 590 | 0A398A9B2022C87A00741F6D /* ItemViewController.xib in Resources */, 591 | 0A398A4420229EBE00741F6D /* Assets.xcassets in Resources */, 592 | 0A398A8D2022AD2D00741F6D /* ItemCell.xib in Resources */, 593 | ); 594 | runOnlyForDeploymentPostprocessing = 0; 595 | }; 596 | 0A398A4B20229EBF00741F6D /* Resources */ = { 597 | isa = PBXResourcesBuildPhase; 598 | buildActionMask = 2147483647; 599 | files = ( 600 | ); 601 | runOnlyForDeploymentPostprocessing = 0; 602 | }; 603 | 0A398A5620229EBF00741F6D /* Resources */ = { 604 | isa = PBXResourcesBuildPhase; 605 | buildActionMask = 2147483647; 606 | files = ( 607 | ); 608 | runOnlyForDeploymentPostprocessing = 0; 609 | }; 610 | /* End PBXResourcesBuildPhase section */ 611 | 612 | /* Begin PBXShellScriptBuildPhase section */ 613 | 39261D78BCE753E58A5015E2 /* [CP] Check Pods Manifest.lock */ = { 614 | isa = PBXShellScriptBuildPhase; 615 | buildActionMask = 2147483647; 616 | files = ( 617 | ); 618 | inputPaths = ( 619 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 620 | "${PODS_ROOT}/Manifest.lock", 621 | ); 622 | name = "[CP] Check Pods Manifest.lock"; 623 | outputPaths = ( 624 | "$(DERIVED_FILE_DIR)/Pods-ToDoUITests-checkManifestLockResult.txt", 625 | ); 626 | runOnlyForDeploymentPostprocessing = 0; 627 | shellPath = /bin/sh; 628 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 629 | showEnvVarsInLog = 0; 630 | }; 631 | 44F2F90FCE6F87CC29EA0CC4 /* [CP] Embed Pods Frameworks */ = { 632 | isa = PBXShellScriptBuildPhase; 633 | buildActionMask = 2147483647; 634 | files = ( 635 | ); 636 | inputPaths = ( 637 | "${PODS_ROOT}/Target Support Files/Pods-ToDo/Pods-ToDo-frameworks.sh", 638 | "${BUILT_PRODUCTS_DIR}/DynamicButton/DynamicButton.framework", 639 | "${BUILT_PRODUCTS_DIR}/Eureka/Eureka.framework", 640 | "${BUILT_PRODUCTS_DIR}/IGListDiffKit/IGListDiffKit.framework", 641 | "${BUILT_PRODUCTS_DIR}/IGListKit/IGListKit.framework", 642 | "${BUILT_PRODUCTS_DIR}/REFrostedViewController/REFrostedViewController.framework", 643 | "${BUILT_PRODUCTS_DIR}/RIBs/RIBs.framework", 644 | "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", 645 | "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", 646 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 647 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 648 | "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", 649 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", 650 | "${BUILT_PRODUCTS_DIR}/TransitionButton/TransitionButton.framework", 651 | ); 652 | name = "[CP] Embed Pods Frameworks"; 653 | outputPaths = ( 654 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DynamicButton.framework", 655 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Eureka.framework", 656 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGListDiffKit.framework", 657 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGListKit.framework", 658 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/REFrostedViewController.framework", 659 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RIBs.framework", 660 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", 661 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", 662 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 663 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 664 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", 665 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", 666 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TransitionButton.framework", 667 | ); 668 | runOnlyForDeploymentPostprocessing = 0; 669 | shellPath = /bin/sh; 670 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ToDo/Pods-ToDo-frameworks.sh\"\n"; 671 | showEnvVarsInLog = 0; 672 | }; 673 | 757A4E7E56EA4FB0AA9CC45A /* [CP] Check Pods Manifest.lock */ = { 674 | isa = PBXShellScriptBuildPhase; 675 | buildActionMask = 2147483647; 676 | files = ( 677 | ); 678 | inputPaths = ( 679 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 680 | "${PODS_ROOT}/Manifest.lock", 681 | ); 682 | name = "[CP] Check Pods Manifest.lock"; 683 | outputPaths = ( 684 | "$(DERIVED_FILE_DIR)/Pods-ToDo-checkManifestLockResult.txt", 685 | ); 686 | runOnlyForDeploymentPostprocessing = 0; 687 | shellPath = /bin/sh; 688 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 689 | showEnvVarsInLog = 0; 690 | }; 691 | E1602153A3D8468ED61169AB /* [CP] Check Pods Manifest.lock */ = { 692 | isa = PBXShellScriptBuildPhase; 693 | buildActionMask = 2147483647; 694 | files = ( 695 | ); 696 | inputPaths = ( 697 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 698 | "${PODS_ROOT}/Manifest.lock", 699 | ); 700 | name = "[CP] Check Pods Manifest.lock"; 701 | outputPaths = ( 702 | "$(DERIVED_FILE_DIR)/Pods-ToDoTests-checkManifestLockResult.txt", 703 | ); 704 | runOnlyForDeploymentPostprocessing = 0; 705 | shellPath = /bin/sh; 706 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 707 | showEnvVarsInLog = 0; 708 | }; 709 | /* End PBXShellScriptBuildPhase section */ 710 | 711 | /* Begin PBXSourcesBuildPhase section */ 712 | 0A398A3520229EBE00741F6D /* Sources */ = { 713 | isa = PBXSourcesBuildPhase; 714 | buildActionMask = 2147483647; 715 | files = ( 716 | 0A398A9F2022D9A200741F6D /* ListComponent+Item.swift in Sources */, 717 | 0A398ABD2023188200741F6D /* LoggedOutInteractor.swift in Sources */, 718 | 0A398ACF20231A6900741F6D /* DrawerBuilder.swift in Sources */, 719 | 0A0CF410202321530010892B /* LoggedInComponent+Drawer.swift in Sources */, 720 | 0A398A802022A4CD00741F6D /* ToDoService.swift in Sources */, 721 | 0A398AA82023139200741F6D /* RootInteractor.swift in Sources */, 722 | 0A398AA52023139200741F6D /* RootRouter.swift in Sources */, 723 | 0A398AC22023188200741F6D /* LoggedInBuilder.swift in Sources */, 724 | 0A0CF4232023DDCD0010892B /* ListActionableItem.swift in Sources */, 725 | 0A398A772022A1B700741F6D /* ListInteractor.swift in Sources */, 726 | 0A0CF40C20231EF20010892B /* DeeplinkWorkflow.swift in Sources */, 727 | 0A398A742022A1B700741F6D /* ListRouter.swift in Sources */, 728 | 0A0CF40820231EF20010892B /* LoggedInActionableItem.swift in Sources */, 729 | 0A398A862022A88A00741F6D /* ItemViewModel.swift in Sources */, 730 | 0A0CF41F2023D0B50010892B /* DrawerComponent+Menu.swift in Sources */, 731 | 0A398A9D2022D7DD00741F6D /* Config.swift in Sources */, 732 | 0A398A762022A1B700741F6D /* ListBuilder.swift in Sources */, 733 | 0A398ABB2023188200741F6D /* LoggedOutViewController.swift in Sources */, 734 | 0A398ABF2023188200741F6D /* LoggedOutRouter.swift in Sources */, 735 | 0A398AA72023139200741F6D /* RootBuilder.swift in Sources */, 736 | 0A0CF41C2023D0940010892B /* MenuInteractor.swift in Sources */, 737 | 0A398AAC2023182800741F6D /* RootComponent+LoggedIn.swift in Sources */, 738 | 0A398A842022A75400741F6D /* LoadingPresentable.swift in Sources */, 739 | 0A398A7D2022A45400741F6D /* Date.swift in Sources */, 740 | 0A398AA62023139200741F6D /* RootViewController.swift in Sources */, 741 | 0A398A982022C87A00741F6D /* ItemViewController.swift in Sources */, 742 | 0A398ABE2023188200741F6D /* LoggedOutBuilder.swift in Sources */, 743 | 0A398AAB2023182800741F6D /* RootComponent+LoggedOut.swift in Sources */, 744 | 0A398ACE20231A6900741F6D /* DrawerRouter.swift in Sources */, 745 | 0A398A3D20229EBE00741F6D /* AppDelegate.swift in Sources */, 746 | 0A0CF40920231EF20010892B /* DrawerActionableItem.swift in Sources */, 747 | 0A398A9A2022C87A00741F6D /* ItemInteractor.swift in Sources */, 748 | 0A398AC42023188200741F6D /* LoggedInInteractor.swift in Sources */, 749 | 0A0CF40A20231EF20010892B /* RootActionableItem.swift in Sources */, 750 | 0A0CF41A2023D0940010892B /* MenuViewController.swift in Sources */, 751 | 0A0CF40E20231FA60010892B /* UINavigationController.swift in Sources */, 752 | 0A398AC32023188200741F6D /* LoggedInRouter.swift in Sources */, 753 | 0A398ACC20231A6900741F6D /* DrawerViewController.swift in Sources */, 754 | 0A398A8C2022AD2D00741F6D /* ItemCell.swift in Sources */, 755 | 0A0CF41B2023D0940010892B /* MenuBuilder.swift in Sources */, 756 | 0A398A7A2022A39D00741F6D /* Item.swift in Sources */, 757 | 0A398A752022A1B700741F6D /* ListViewController.swift in Sources */, 758 | 0A398A992022C87A00741F6D /* ItemBuilder.swift in Sources */, 759 | 0A0CF40B20231EF20010892B /* RootWorkflow.swift in Sources */, 760 | 0A0CF412202324460010892B /* DrawerComponent+List.swift in Sources */, 761 | 0A398A6D2022A15F00741F6D /* AppComponent.swift in Sources */, 762 | 0A0CF4192023D0940010892B /* MenuRouter.swift in Sources */, 763 | 0A398ACD20231A6900741F6D /* DrawerInteractor.swift in Sources */, 764 | 0A398A972022C87A00741F6D /* ItemRouter.swift in Sources */, 765 | ); 766 | runOnlyForDeploymentPostprocessing = 0; 767 | }; 768 | 0A398A4920229EBF00741F6D /* Sources */ = { 769 | isa = PBXSourcesBuildPhase; 770 | buildActionMask = 2147483647; 771 | files = ( 772 | 0A398A5220229EBF00741F6D /* ToDoTests.swift in Sources */, 773 | ); 774 | runOnlyForDeploymentPostprocessing = 0; 775 | }; 776 | 0A398A5420229EBF00741F6D /* Sources */ = { 777 | isa = PBXSourcesBuildPhase; 778 | buildActionMask = 2147483647; 779 | files = ( 780 | 0A398A5D20229EBF00741F6D /* ToDoUITests.swift in Sources */, 781 | ); 782 | runOnlyForDeploymentPostprocessing = 0; 783 | }; 784 | /* End PBXSourcesBuildPhase section */ 785 | 786 | /* Begin PBXTargetDependency section */ 787 | 0A398A4F20229EBF00741F6D /* PBXTargetDependency */ = { 788 | isa = PBXTargetDependency; 789 | target = 0A398A3820229EBE00741F6D /* ToDo */; 790 | targetProxy = 0A398A4E20229EBF00741F6D /* PBXContainerItemProxy */; 791 | }; 792 | 0A398A5A20229EBF00741F6D /* PBXTargetDependency */ = { 793 | isa = PBXTargetDependency; 794 | target = 0A398A3820229EBE00741F6D /* ToDo */; 795 | targetProxy = 0A398A5920229EBF00741F6D /* PBXContainerItemProxy */; 796 | }; 797 | /* End PBXTargetDependency section */ 798 | 799 | /* Begin PBXVariantGroup section */ 800 | 0A398A4520229EBE00741F6D /* LaunchScreen.storyboard */ = { 801 | isa = PBXVariantGroup; 802 | children = ( 803 | 0A398A4620229EBE00741F6D /* Base */, 804 | ); 805 | name = LaunchScreen.storyboard; 806 | sourceTree = ""; 807 | }; 808 | /* End PBXVariantGroup section */ 809 | 810 | /* Begin XCBuildConfiguration section */ 811 | 0A398A5F20229EBF00741F6D /* Debug */ = { 812 | isa = XCBuildConfiguration; 813 | buildSettings = { 814 | ALWAYS_SEARCH_USER_PATHS = NO; 815 | CLANG_ANALYZER_NONNULL = YES; 816 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 817 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 818 | CLANG_CXX_LIBRARY = "libc++"; 819 | CLANG_ENABLE_MODULES = YES; 820 | CLANG_ENABLE_OBJC_ARC = YES; 821 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 822 | CLANG_WARN_BOOL_CONVERSION = YES; 823 | CLANG_WARN_COMMA = YES; 824 | CLANG_WARN_CONSTANT_CONVERSION = YES; 825 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 826 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 827 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 828 | CLANG_WARN_EMPTY_BODY = YES; 829 | CLANG_WARN_ENUM_CONVERSION = YES; 830 | CLANG_WARN_INFINITE_RECURSION = YES; 831 | CLANG_WARN_INT_CONVERSION = YES; 832 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 833 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 834 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 835 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 836 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 837 | CLANG_WARN_STRICT_PROTOTYPES = YES; 838 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 839 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 840 | CLANG_WARN_UNREACHABLE_CODE = YES; 841 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 842 | CODE_SIGN_IDENTITY = "iPhone Developer"; 843 | COPY_PHASE_STRIP = NO; 844 | DEBUG_INFORMATION_FORMAT = dwarf; 845 | ENABLE_STRICT_OBJC_MSGSEND = YES; 846 | ENABLE_TESTABILITY = YES; 847 | GCC_C_LANGUAGE_STANDARD = gnu11; 848 | GCC_DYNAMIC_NO_PIC = NO; 849 | GCC_NO_COMMON_BLOCKS = YES; 850 | GCC_OPTIMIZATION_LEVEL = 0; 851 | GCC_PREPROCESSOR_DEFINITIONS = ( 852 | "DEBUG=1", 853 | "$(inherited)", 854 | ); 855 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 856 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 857 | GCC_WARN_UNDECLARED_SELECTOR = YES; 858 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 859 | GCC_WARN_UNUSED_FUNCTION = YES; 860 | GCC_WARN_UNUSED_VARIABLE = YES; 861 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 862 | MTL_ENABLE_DEBUG_INFO = YES; 863 | ONLY_ACTIVE_ARCH = YES; 864 | SDKROOT = iphoneos; 865 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 866 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 867 | SWIFT_VERSION = 4.2; 868 | }; 869 | name = Debug; 870 | }; 871 | 0A398A6020229EBF00741F6D /* Release */ = { 872 | isa = XCBuildConfiguration; 873 | buildSettings = { 874 | ALWAYS_SEARCH_USER_PATHS = NO; 875 | CLANG_ANALYZER_NONNULL = YES; 876 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 877 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 878 | CLANG_CXX_LIBRARY = "libc++"; 879 | CLANG_ENABLE_MODULES = YES; 880 | CLANG_ENABLE_OBJC_ARC = YES; 881 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 882 | CLANG_WARN_BOOL_CONVERSION = YES; 883 | CLANG_WARN_COMMA = YES; 884 | CLANG_WARN_CONSTANT_CONVERSION = YES; 885 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 886 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 887 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 888 | CLANG_WARN_EMPTY_BODY = YES; 889 | CLANG_WARN_ENUM_CONVERSION = YES; 890 | CLANG_WARN_INFINITE_RECURSION = YES; 891 | CLANG_WARN_INT_CONVERSION = YES; 892 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 893 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 894 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 895 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 896 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 897 | CLANG_WARN_STRICT_PROTOTYPES = YES; 898 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 899 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 900 | CLANG_WARN_UNREACHABLE_CODE = YES; 901 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 902 | CODE_SIGN_IDENTITY = "iPhone Developer"; 903 | COPY_PHASE_STRIP = NO; 904 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 905 | ENABLE_NS_ASSERTIONS = NO; 906 | ENABLE_STRICT_OBJC_MSGSEND = YES; 907 | GCC_C_LANGUAGE_STANDARD = gnu11; 908 | GCC_NO_COMMON_BLOCKS = YES; 909 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 910 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 911 | GCC_WARN_UNDECLARED_SELECTOR = YES; 912 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 913 | GCC_WARN_UNUSED_FUNCTION = YES; 914 | GCC_WARN_UNUSED_VARIABLE = YES; 915 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 916 | MTL_ENABLE_DEBUG_INFO = NO; 917 | SDKROOT = iphoneos; 918 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 919 | SWIFT_VERSION = 4.2; 920 | VALIDATE_PRODUCT = YES; 921 | }; 922 | name = Release; 923 | }; 924 | 0A398A6220229EBF00741F6D /* Debug */ = { 925 | isa = XCBuildConfiguration; 926 | baseConfigurationReference = CE013D3E86FEFF3D06480E67 /* Pods-ToDo.debug.xcconfig */; 927 | buildSettings = { 928 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 929 | CODE_SIGN_STYLE = Automatic; 930 | INFOPLIST_FILE = "$(SRCROOT)/ToDo/Resources/Info.plist"; 931 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 932 | PRODUCT_BUNDLE_IDENTIFIER = "au.com.dev4jam-todo-ios"; 933 | PRODUCT_NAME = "$(TARGET_NAME)"; 934 | SWIFT_VERSION = 5.0; 935 | TARGETED_DEVICE_FAMILY = "1,2"; 936 | }; 937 | name = Debug; 938 | }; 939 | 0A398A6320229EBF00741F6D /* Release */ = { 940 | isa = XCBuildConfiguration; 941 | baseConfigurationReference = 03605E0BDA10357A4A3F9F4D /* Pods-ToDo.release.xcconfig */; 942 | buildSettings = { 943 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 944 | CODE_SIGN_STYLE = Automatic; 945 | INFOPLIST_FILE = "$(SRCROOT)/ToDo/Resources/Info.plist"; 946 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 947 | PRODUCT_BUNDLE_IDENTIFIER = "au.com.dev4jam-todo-ios"; 948 | PRODUCT_NAME = "$(TARGET_NAME)"; 949 | SWIFT_VERSION = 5.0; 950 | TARGETED_DEVICE_FAMILY = "1,2"; 951 | }; 952 | name = Release; 953 | }; 954 | 0A398A6520229EBF00741F6D /* Debug */ = { 955 | isa = XCBuildConfiguration; 956 | baseConfigurationReference = 100052882F75295A287FAA47 /* Pods-ToDoTests.debug.xcconfig */; 957 | buildSettings = { 958 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 959 | BUNDLE_LOADER = "$(TEST_HOST)"; 960 | CODE_SIGN_STYLE = Automatic; 961 | INFOPLIST_FILE = ToDoTests/Info.plist; 962 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 963 | PRODUCT_BUNDLE_IDENTIFIER = "au.com.westpac.todo-ios.ToDoTests"; 964 | PRODUCT_NAME = "$(TARGET_NAME)"; 965 | SWIFT_VERSION = 4.2; 966 | TARGETED_DEVICE_FAMILY = "1,2"; 967 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ToDo.app/ToDo"; 968 | }; 969 | name = Debug; 970 | }; 971 | 0A398A6620229EBF00741F6D /* Release */ = { 972 | isa = XCBuildConfiguration; 973 | baseConfigurationReference = E9FA3507A60CD3E496C7878A /* Pods-ToDoTests.release.xcconfig */; 974 | buildSettings = { 975 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 976 | BUNDLE_LOADER = "$(TEST_HOST)"; 977 | CODE_SIGN_STYLE = Automatic; 978 | INFOPLIST_FILE = ToDoTests/Info.plist; 979 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 980 | PRODUCT_BUNDLE_IDENTIFIER = "au.com.westpac.todo-ios.ToDoTests"; 981 | PRODUCT_NAME = "$(TARGET_NAME)"; 982 | SWIFT_VERSION = 4.2; 983 | TARGETED_DEVICE_FAMILY = "1,2"; 984 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ToDo.app/ToDo"; 985 | }; 986 | name = Release; 987 | }; 988 | 0A398A6820229EBF00741F6D /* Debug */ = { 989 | isa = XCBuildConfiguration; 990 | baseConfigurationReference = 33B0C1192983C4F2C0FD30CF /* Pods-ToDoUITests.debug.xcconfig */; 991 | buildSettings = { 992 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 993 | CODE_SIGN_STYLE = Automatic; 994 | INFOPLIST_FILE = ToDoUITests/Info.plist; 995 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 996 | PRODUCT_BUNDLE_IDENTIFIER = "au.com.westpac.todo-ios.ToDoUITests"; 997 | PRODUCT_NAME = "$(TARGET_NAME)"; 998 | SWIFT_VERSION = 4.2; 999 | TARGETED_DEVICE_FAMILY = "1,2"; 1000 | TEST_TARGET_NAME = ToDo; 1001 | }; 1002 | name = Debug; 1003 | }; 1004 | 0A398A6920229EBF00741F6D /* Release */ = { 1005 | isa = XCBuildConfiguration; 1006 | baseConfigurationReference = 4671FC4DBBB94037D17F2066 /* Pods-ToDoUITests.release.xcconfig */; 1007 | buildSettings = { 1008 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 1009 | CODE_SIGN_STYLE = Automatic; 1010 | INFOPLIST_FILE = ToDoUITests/Info.plist; 1011 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1012 | PRODUCT_BUNDLE_IDENTIFIER = "au.com.westpac.todo-ios.ToDoUITests"; 1013 | PRODUCT_NAME = "$(TARGET_NAME)"; 1014 | SWIFT_VERSION = 4.2; 1015 | TARGETED_DEVICE_FAMILY = "1,2"; 1016 | TEST_TARGET_NAME = ToDo; 1017 | }; 1018 | name = Release; 1019 | }; 1020 | /* End XCBuildConfiguration section */ 1021 | 1022 | /* Begin XCConfigurationList section */ 1023 | 0A398A3420229EBE00741F6D /* Build configuration list for PBXProject "ToDo" */ = { 1024 | isa = XCConfigurationList; 1025 | buildConfigurations = ( 1026 | 0A398A5F20229EBF00741F6D /* Debug */, 1027 | 0A398A6020229EBF00741F6D /* Release */, 1028 | ); 1029 | defaultConfigurationIsVisible = 0; 1030 | defaultConfigurationName = Release; 1031 | }; 1032 | 0A398A6120229EBF00741F6D /* Build configuration list for PBXNativeTarget "ToDo" */ = { 1033 | isa = XCConfigurationList; 1034 | buildConfigurations = ( 1035 | 0A398A6220229EBF00741F6D /* Debug */, 1036 | 0A398A6320229EBF00741F6D /* Release */, 1037 | ); 1038 | defaultConfigurationIsVisible = 0; 1039 | defaultConfigurationName = Release; 1040 | }; 1041 | 0A398A6420229EBF00741F6D /* Build configuration list for PBXNativeTarget "ToDoTests" */ = { 1042 | isa = XCConfigurationList; 1043 | buildConfigurations = ( 1044 | 0A398A6520229EBF00741F6D /* Debug */, 1045 | 0A398A6620229EBF00741F6D /* Release */, 1046 | ); 1047 | defaultConfigurationIsVisible = 0; 1048 | defaultConfigurationName = Release; 1049 | }; 1050 | 0A398A6720229EBF00741F6D /* Build configuration list for PBXNativeTarget "ToDoUITests" */ = { 1051 | isa = XCConfigurationList; 1052 | buildConfigurations = ( 1053 | 0A398A6820229EBF00741F6D /* Debug */, 1054 | 0A398A6920229EBF00741F6D /* Release */, 1055 | ); 1056 | defaultConfigurationIsVisible = 0; 1057 | defaultConfigurationName = Release; 1058 | }; 1059 | /* End XCConfigurationList section */ 1060 | }; 1061 | rootObject = 0A398A3120229EBE00741F6D /* Project object */; 1062 | } 1063 | -------------------------------------------------------------------------------- /ToDo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ToDo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ToDo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ToDo/App/AppComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppComponent.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RIBs 11 | import RxSwift 12 | 13 | final class AppComponent: Component, RootDependency { 14 | let application: UIApplication 15 | let launchOptions: [UIApplication.LaunchOptionsKey : Any]? 16 | let service: ToDoServiceProtocol 17 | let config: Variable 18 | 19 | 20 | init(application: UIApplication, launchOptions: [UIApplication.LaunchOptionsKey : Any]?) { 21 | let configSnapshot = Config(maxIncompleteItems: 10) 22 | 23 | self.application = application 24 | self.launchOptions = launchOptions 25 | self.service = ToDoService() 26 | self.config = Variable(configSnapshot) 27 | 28 | super.init(dependency: EmptyComponent()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ToDo/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RIBs 11 | 12 | protocol UrlHandler: class { 13 | func handle(_ url: URL) -> Bool 14 | } 15 | 16 | @UIApplicationMain 17 | class AppDelegate: UIResponder, UIApplicationDelegate { 18 | 19 | var window: UIWindow? 20 | 21 | private var launchRouter: LaunchRouting? 22 | private var urlHandler: UrlHandler? 23 | 24 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 25 | // Override point for customization after application launch. 26 | 27 | let window = UIWindow(frame: UIScreen.main.bounds) 28 | 29 | self.window = window 30 | 31 | let appComponent = AppComponent(application: application, launchOptions: launchOptions) 32 | let rib = RootBuilder(dependency: appComponent).build() 33 | 34 | launchRouter = rib.launchRouter 35 | urlHandler = rib.urlHandler 36 | 37 | launchRouter?.launchFromWindow(window) 38 | 39 | return true 40 | } 41 | 42 | func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { 43 | return urlHandler?.handle(url) ?? false 44 | } 45 | 46 | func applicationWillResignActive(_ application: UIApplication) { 47 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 48 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 49 | } 50 | 51 | func applicationDidEnterBackground(_ application: UIApplication) { 52 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 53 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 54 | } 55 | 56 | func applicationWillEnterForeground(_ application: UIApplication) { 57 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 58 | } 59 | 60 | func applicationDidBecomeActive(_ application: UIApplication) { 61 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 62 | } 63 | 64 | func applicationWillTerminate(_ application: UIApplication) { 65 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /ToDo/Models/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Config { 12 | let maxIncompleteItems: Int 13 | 14 | var appVersion: String { 15 | var defaultVersion = "1.0" 16 | 17 | if let dictionary = Bundle.main.infoDictionary { 18 | if let version = dictionary["CFBundleShortVersionString"] as? String { 19 | defaultVersion = version 20 | } 21 | } 22 | 23 | return defaultVersion 24 | } 25 | 26 | var buildNumber: String { 27 | var defaultNumber = "1" 28 | 29 | if let dictionary = Bundle.main.infoDictionary { 30 | if let build = dictionary["CFBundleVersion"] as? String { 31 | defaultNumber = build 32 | } 33 | } 34 | 35 | return defaultNumber 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ToDo/Models/Item.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Item.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Item { 12 | let id: Int 13 | let createdAt: Date 14 | let details: String 15 | let isComplete: Bool 16 | } 17 | -------------------------------------------------------------------------------- /ToDo/Models/ItemViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemViewModel.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final class ItemViewModel { 13 | let model: Item 14 | private static let dateFormatter = DateFormatter() 15 | 16 | init(model: Item) { 17 | self.model = model 18 | } 19 | 20 | var key: Int { 21 | return model.id 22 | } 23 | 24 | var isComplete: Bool { 25 | return model.isComplete 26 | } 27 | 28 | var statusColor: UIColor { 29 | return isComplete ? .blue : .green 30 | } 31 | 32 | var details: String { 33 | return model.details 34 | } 35 | 36 | var createdAt: String { 37 | get { 38 | ItemViewModel.dateFormatter.dateStyle = .short 39 | ItemViewModel.dateFormatter.timeStyle = .short 40 | 41 | return ItemViewModel.dateFormatter.string(from: model.createdAt) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ToDo/RIBs/ActionableItems/DrawerActionableItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawerActionableItem.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | public protocol DrawerActionableItem: class { 12 | func waitForList() -> Observable<(ListActionableItem, ())> 13 | func sayHi() -> Observable<(ListActionableItem, ())> 14 | } 15 | 16 | -------------------------------------------------------------------------------- /ToDo/RIBs/ActionableItems/ListActionableItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListActionableItem.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 2/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | public protocol ListActionableItem: class { 12 | func createTask(with description: String) -> Observable<(ListActionableItem, ())> 13 | func createBlankTask() -> Observable<(ListActionableItem, ())> 14 | func doNothing() -> Observable<(ListActionableItem, ())> 15 | } 16 | -------------------------------------------------------------------------------- /ToDo/RIBs/ActionableItems/LoggedInActionableItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedInActionableItem.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | public protocol LoggedInActionableItem: class { 12 | func waitForDrawer() -> Observable<(DrawerActionableItem, ())> 13 | } 14 | -------------------------------------------------------------------------------- /ToDo/RIBs/ActionableItems/RootActionableItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootActionableItem.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | public protocol RootActionableItem: class { 12 | func waitForLogin() -> Observable<(LoggedInActionableItem, ())> 13 | } 14 | -------------------------------------------------------------------------------- /ToDo/RIBs/Common/LoadingPresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingPresentable.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SVProgressHUD 11 | 12 | protocol LoadingPresentable: class { 13 | func showLoading(with status: String?) 14 | func showSuccess(with status: String?) 15 | func showError(with status: String?) 16 | 17 | func hideLoading() 18 | } 19 | 20 | extension UIViewController: LoadingPresentable { 21 | func showLoading(with status: String?) { 22 | SVProgressHUD.show(withStatus: status) 23 | } 24 | 25 | func showSuccess(with status: String?) { 26 | SVProgressHUD.showSuccess(withStatus: status) 27 | } 28 | 29 | func showError(with status: String?) { 30 | SVProgressHUD.showError(withStatus: status) 31 | } 32 | 33 | func hideLoading() { 34 | SVProgressHUD.dismiss() 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /ToDo/RIBs/Common/UINavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UINavigationController.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RIBs 11 | 12 | extension UINavigationController: ViewControllable { 13 | public var uiviewController: UIViewController { return self } 14 | 15 | public convenience init(root: ViewControllable) { 16 | self.init(rootViewController: root.uiviewController) 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /ToDo/RIBs/Drawer/DrawerBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedOutInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol DrawerDependency: Dependency { 13 | // TODO: Declare the set of dependencies required by this RIB, but cannot be 14 | // created by this RIB. 15 | 16 | var service: ToDoServiceProtocol { get } 17 | var config: Variable { get } 18 | } 19 | 20 | final class DrawerComponent: Component { 21 | var service: ToDoServiceProtocol { 22 | return dependency.service 23 | } 24 | 25 | var config: Variable { 26 | return dependency.config 27 | } 28 | } 29 | 30 | // MARK: - Builder 31 | 32 | protocol DrawerBuildable: Buildable { 33 | func build(withListener listener: DrawerListener) -> (router: DrawerRouting, actionableItem: DrawerActionableItem) 34 | } 35 | 36 | final class DrawerBuilder: Builder, DrawerBuildable { 37 | 38 | override init(dependency: DrawerDependency) { 39 | super.init(dependency: dependency) 40 | } 41 | 42 | func build(withListener listener: DrawerListener) -> (router: DrawerRouting, actionableItem: DrawerActionableItem) { 43 | let screenWidth = UIScreen.main.bounds.size.width 44 | var menuWidth: CGFloat = screenWidth - 75.0 45 | 46 | if screenWidth > 375 { 47 | menuWidth = screenWidth - 150.0 48 | } else if screenWidth > 320 { 49 | menuWidth = screenWidth - 82.0 50 | } 51 | 52 | let component = DrawerComponent(dependency: dependency) 53 | let viewController = DrawerViewController(menuWidth: menuWidth) 54 | let interactor = DrawerInteractor(presenter: viewController) 55 | 56 | interactor.listener = listener 57 | 58 | let menuBuilder = MenuBuilder(dependency: component) 59 | let listBuilder = ListBuilder(dependency: component) 60 | 61 | let router = DrawerRouter(interactor: interactor, viewController: viewController, 62 | menuBuilder: menuBuilder, listBuilder: listBuilder) 63 | 64 | return (router, interactor) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ToDo/RIBs/Drawer/DrawerComponent+List.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawerComponent+List.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | /// The dependencies needed from the parent scope of Drawer to provide for the List scope. 12 | // TODO: Update DrawerDependency protocol to inherit this protocol. 13 | protocol DrawerDependencyList: Dependency { 14 | // TODO: Declare dependencies needed from the parent scope of Drawer to provide dependencies 15 | // for the List scope. 16 | } 17 | 18 | extension DrawerComponent: ListDependency { 19 | // TODO: Implement properties to provide for List scope. 20 | } 21 | -------------------------------------------------------------------------------- /ToDo/RIBs/Drawer/DrawerComponent+Menu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawerComponent+Menu.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 2/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | /// The dependencies needed from the parent scope of Drawer to provide for the Menu scope. 12 | // TODO: Update DrawerDependency protocol to inherit this protocol. 13 | protocol DrawerDependencyMenu: Dependency { 14 | // TODO: Declare dependencies needed from the parent scope of Drawer to provide dependencies 15 | // for the Menu scope. 16 | } 17 | 18 | extension DrawerComponent: MenuDependency { 19 | 20 | // TODO: Implement properties to provide for Menu scope. 21 | } 22 | -------------------------------------------------------------------------------- /ToDo/RIBs/Drawer/DrawerInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedOutInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol DrawerRouting: ViewableRouting { 13 | // TODO: Declare methods the interactor can invoke to manage sub-tree via the router. 14 | 15 | func showContent() -> ListActionableItem 16 | } 17 | 18 | protocol DrawerPresentable: Presentable { 19 | var listener: DrawerPresentableListener? { get set } 20 | // TODO: Declare methods the interactor can invoke the presenter to present data. 21 | 22 | func showSelectedMenuItem(_ menuItem: String) 23 | func showMenu() 24 | func hideMenu() 25 | func showHi() 26 | } 27 | 28 | protocol DrawerListener: class { 29 | // TODO: Declare methods the interactor can invoke to communicate with other RIBs. 30 | 31 | func drawerDidAppear() 32 | } 33 | 34 | final class DrawerInteractor: PresentableInteractor, DrawerInteractable, 35 | DrawerPresentableListener, DrawerActionableItem { 36 | 37 | weak var router: DrawerRouting? 38 | weak var listener: DrawerListener? 39 | 40 | var listActionableItem: ListActionableItem? 41 | 42 | private var isMenuOpened: Bool = false 43 | private let listActionableItemSubject = ReplaySubject.create(bufferSize: 1) 44 | 45 | // TODO: Add additional dependencies to constructor. Do not perform any logic 46 | // in constructor. 47 | override init(presenter: DrawerPresentable) { 48 | super.init(presenter: presenter) 49 | 50 | presenter.listener = self 51 | } 52 | 53 | override func didBecomeActive() { 54 | super.didBecomeActive() 55 | // TODO: Implement business logic here. 56 | 57 | listActionableItem = router?.showContent() 58 | } 59 | 60 | override func willResignActive() { 61 | super.willResignActive() 62 | // TODO: Pause any business logic. 63 | } 64 | 65 | // MARK: - DrawerPresentableListener 66 | 67 | func didOpenMenu() { 68 | isMenuOpened = true 69 | } 70 | 71 | func didCloseMenu() { 72 | isMenuOpened = false 73 | } 74 | 75 | func didAppear() { 76 | listener?.drawerDidAppear() 77 | } 78 | 79 | // MARK: - MenuListener 80 | 81 | func didSelectMenu(_ menuItem: String) { 82 | presenter.hideMenu() 83 | presenter.showSelectedMenuItem(menuItem) 84 | } 85 | 86 | // MARK: - ListListener 87 | func listDidAppear() { 88 | if let listActionableItem = listActionableItem { 89 | listActionableItemSubject.onNext(listActionableItem) 90 | } 91 | 92 | listActionableItem = nil 93 | } 94 | 95 | func didSelectMenu() { 96 | presenter.showMenu() 97 | } 98 | 99 | // MARK: - DrawerActionableItem 100 | 101 | func waitForList() -> Observable<(ListActionableItem, ())> { 102 | return listActionableItemSubject 103 | .map { (listItem: ListActionableItem) -> (ListActionableItem, ()) in 104 | (listItem, ()) 105 | } 106 | } 107 | 108 | func sayHi() -> Observable<(ListActionableItem, ())> { 109 | presenter.showHi() 110 | 111 | return listActionableItemSubject 112 | .map { (listItem: ListActionableItem) -> (ListActionableItem, ()) in 113 | (listItem, ()) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /ToDo/RIBs/Drawer/DrawerRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedOutInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol DrawerInteractable: Interactable, MenuListener, ListListener { 13 | 14 | var router: DrawerRouting? { get set } 15 | var listener: DrawerListener? { get set } 16 | } 17 | 18 | protocol DrawerViewControllable: ViewControllable { 19 | // TODO: Declare methods the router invokes to manipulate the view hierarchy. 20 | 21 | func presentContent(viewController: ViewControllable) 22 | func presentMenu(viewController: ViewControllable) 23 | } 24 | 25 | final class DrawerRouter: ViewableRouter, DrawerRouting { 26 | private var menuBuilder: MenuBuildable 27 | private var listBuilder: ListBuildable 28 | 29 | private (set) var menuItemChild: ViewableRouting? 30 | 31 | // TODO: Constructor inject child builder protocols to allow building children. 32 | required init(interactor: DrawerInteractable, viewController: DrawerViewControllable, 33 | menuBuilder: MenuBuildable, listBuilder: ListBuildable) { 34 | 35 | self.menuBuilder = menuBuilder 36 | self.listBuilder = listBuilder 37 | 38 | super.init(interactor: interactor, viewController: viewController) 39 | 40 | interactor.router = self 41 | } 42 | 43 | override func didLoad() { 44 | super.didLoad() 45 | 46 | attachMenu() 47 | } 48 | 49 | func showContent() -> ListActionableItem { 50 | let rib = listBuilder.build(withListener: interactor) 51 | 52 | attachChild(rib.router) 53 | 54 | let navController = UINavigationController(root: rib.router.viewControllable) 55 | 56 | viewController.presentContent(viewController: navController) 57 | 58 | return rib.actionableItem 59 | } 60 | 61 | func attachMenu() { 62 | let rib = menuBuilder.build(withListener: interactor) 63 | 64 | attachChild(rib) 65 | 66 | viewController.presentMenu(viewController: rib.viewControllable) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ToDo/RIBs/Drawer/DrawerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedOutInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | import UIKit 12 | import REFrostedViewController 13 | 14 | protocol DrawerPresentableListener: class { 15 | // TODO: Declare properties and methods that the view controller can invoke to perform 16 | // business logic, such as signIn(). This protocol is implemented by the corresponding 17 | // interactor class. 18 | 19 | func didOpenMenu() 20 | func didCloseMenu() 21 | func didAppear() 22 | } 23 | 24 | final class DrawerViewController: REFrostedViewController, DrawerPresentable, DrawerViewControllable { 25 | 26 | /// The UIKit view representation of this view. 27 | public final var uiviewController: UIViewController { return self } 28 | 29 | weak var listener: DrawerPresentableListener? 30 | 31 | init(menuWidth: CGFloat) { 32 | super.init(contentViewController: nil, menuViewController: nil) 33 | 34 | let screenHeight = UIScreen.main.bounds.size.height 35 | 36 | self.direction = .right 37 | self.liveBlurBackgroundStyle = .dark 38 | self.liveBlur = true 39 | self.panGestureEnabled = true 40 | self.limitMenuViewSize = true 41 | self.menuViewSize = CGSize(width: menuWidth, height: screenHeight) 42 | self.panGestureRecognizer.delegate = self 43 | self.delegate = self 44 | } 45 | 46 | override func viewDidAppear(_ animated: Bool) { 47 | super.viewDidAppear(animated) 48 | 49 | listener?.didAppear() 50 | } 51 | 52 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 53 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 54 | } 55 | 56 | required init?(coder aDecoder: NSCoder) { 57 | fatalError("init(coder:) has not been implemented") 58 | } 59 | 60 | func presentContent(viewController: ViewControllable) { 61 | contentViewController = viewController.uiviewController 62 | } 63 | 64 | func presentMenu(viewController: ViewControllable) { 65 | viewController.uiviewController.view.frame = CGRect(x: 0, y: 0, 66 | width: menuViewSize.width, 67 | height: menuViewSize.height) 68 | menuViewController = viewController.uiviewController 69 | } 70 | 71 | func presentMenuItem(viewController: ViewControllable) { 72 | present(viewController.uiviewController, animated: true) 73 | } 74 | 75 | func dismissMenuItem(viewController: ViewControllable) { 76 | viewController.uiviewController.dismiss(animated: true, completion: nil) 77 | } 78 | 79 | // MARK: - DrawerPresentable 80 | 81 | func showMenu() { 82 | presentMenuViewController() 83 | } 84 | 85 | func hideMenu() { 86 | hideMenuViewController() 87 | } 88 | 89 | func showHi() { 90 | let alert = UIAlertController(title: "Hi! 👋🏻", message: nil, preferredStyle: .alert) 91 | 92 | alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 93 | 94 | present(alert, animated: true, completion: nil) 95 | } 96 | 97 | func showSelectedMenuItem(_ menuItem: String) { 98 | let alert = UIAlertController(title: "You've selected:", message: menuItem, preferredStyle: .alert) 99 | 100 | alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 101 | 102 | present(alert, animated: true, completion: nil) 103 | } 104 | } 105 | 106 | // MARK: - UIGestureRecognizerDelegate 107 | 108 | extension DrawerViewController: UIGestureRecognizerDelegate { 109 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, 110 | shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 111 | 112 | guard let view = gestureRecognizer.view else { return true } 113 | 114 | if view.isKind(of: UICollectionView.self) { 115 | return false 116 | } else { 117 | return true 118 | } 119 | } 120 | } 121 | 122 | // MARK: - REFrostedViewControllerDelegate 123 | 124 | extension DrawerViewController: REFrostedViewControllerDelegate { 125 | func frostedViewController(_ frostedViewController: REFrostedViewController!, 126 | didShowMenuViewController menuViewController: UIViewController!) { 127 | listener?.didOpenMenu() 128 | } 129 | 130 | func frostedViewController(_ frostedViewController: REFrostedViewController!, 131 | didHideMenuViewController menuViewController: UIViewController!) { 132 | listener?.didCloseMenu() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /ToDo/RIBs/Item/ItemBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemBuilder.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol ItemDependency: Dependency { 13 | // TODO: Declare the set of dependencies required by this RIB, but cannot be 14 | // created by this RIB. 15 | 16 | var config: Variable { get } 17 | var service: ToDoServiceProtocol { get } 18 | } 19 | 20 | final class ItemComponent: Component { 21 | 22 | // TODO: Declare 'fileprivate' dependencies that are only used by this RIB. 23 | 24 | var config: Variable { 25 | return dependency.config 26 | } 27 | 28 | var service: ToDoServiceProtocol { 29 | return dependency.service 30 | } 31 | } 32 | 33 | // MARK: - Builder 34 | 35 | protocol ItemBuildable: Buildable { 36 | func build(withListener listener: ItemListener, item: ItemViewModel) -> ItemRouting 37 | } 38 | 39 | final class ItemBuilder: Builder, ItemBuildable { 40 | 41 | override init(dependency: ItemDependency) { 42 | super.init(dependency: dependency) 43 | } 44 | 45 | func build(withListener listener: ItemListener, item: ItemViewModel) -> ItemRouting { 46 | let component = ItemComponent(dependency: dependency) 47 | let viewController = ItemViewController() 48 | let interactor = ItemInteractor(presenter: viewController, item: item, 49 | service: component.service, 50 | config: component.config) 51 | interactor.listener = listener 52 | 53 | return ItemRouter(interactor: interactor, viewController: viewController) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ToDo/RIBs/Item/ItemInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol ItemRouting: ViewableRouting { 13 | // TODO: Declare methods the interactor can invoke to manage sub-tree via the router. 14 | } 15 | 16 | protocol ItemPresentable: Presentable { 17 | var listener: ItemPresentableListener? { get set } 18 | // TODO: Declare methods the interactor can invoke the presenter to present data. 19 | 20 | func showTitle(_ title: String) 21 | func showDetails(_ value: String) 22 | } 23 | 24 | protocol ItemListener: class { 25 | // TODO: Declare methods the interactor can invoke to communicate with other RIBs. 26 | 27 | func closeItem() 28 | func didUpdateItemDetails(_ item: ItemViewModel) 29 | func didMarkAsDone(_ item: ItemViewModel) 30 | } 31 | 32 | final class ItemInteractor: PresentableInteractor, ItemInteractable, ItemPresentableListener { 33 | 34 | weak var router: ItemRouting? 35 | weak var listener: ItemListener? 36 | 37 | private let item: ItemViewModel 38 | private let service: ToDoServiceProtocol 39 | private let config: Variable 40 | private var newDetails: String 41 | 42 | // TODO: Add additional dependencies to constructor. Do not perform any logic 43 | // in constructor. 44 | init(presenter: ItemPresentable, item: ItemViewModel, 45 | service: ToDoServiceProtocol, config: Variable) { 46 | 47 | self.item = item 48 | self.config = config 49 | self.service = service 50 | self.newDetails = item.details 51 | 52 | super.init(presenter: presenter) 53 | 54 | presenter.listener = self 55 | } 56 | 57 | override func didBecomeActive() { 58 | super.didBecomeActive() 59 | // TODO: Implement business logic here. 60 | } 61 | 62 | override func willResignActive() { 63 | super.willResignActive() 64 | // TODO: Pause any business logic. 65 | } 66 | 67 | // MARK: - ItemPresentableListener 68 | 69 | func didNavigateBack() { 70 | listener?.closeItem() 71 | } 72 | 73 | func didPrepareView() { 74 | presenter.showTitle("ToDo") 75 | presenter.showDetails(item.details) 76 | } 77 | 78 | func didChangeDetails(_ value: String) { 79 | newDetails = value 80 | } 81 | 82 | func didMarkAsDone() { 83 | let newItem = Item(id: item.key, 84 | createdAt: item.model.createdAt, 85 | details: newDetails, 86 | isComplete: true) 87 | 88 | listener?.didMarkAsDone(ItemViewModel(model: newItem)) 89 | } 90 | 91 | func didSave() { 92 | let newItem = Item(id: item.key, 93 | createdAt: item.model.createdAt, 94 | details: newDetails, 95 | isComplete: item.model.isComplete) 96 | 97 | listener?.didUpdateItemDetails(ItemViewModel(model: newItem)) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /ToDo/RIBs/Item/ItemRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemRouter.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | protocol ItemInteractable: Interactable { 12 | var router: ItemRouting? { get set } 13 | var listener: ItemListener? { get set } 14 | } 15 | 16 | protocol ItemViewControllable: ViewControllable { 17 | // TODO: Declare methods the router invokes to manipulate the view hierarchy. 18 | } 19 | 20 | final class ItemRouter: ViewableRouter, ItemRouting { 21 | 22 | // TODO: Constructor inject child builder protocols to allow building children. 23 | override init(interactor: ItemInteractable, viewController: ItemViewControllable) { 24 | super.init(interactor: interactor, viewController: viewController) 25 | interactor.router = self 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ToDo/RIBs/Item/ItemViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemViewController.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | import UIKit 12 | 13 | protocol ItemPresentableListener: class { 14 | // TODO: Declare properties and methods that the view controller can invoke to perform 15 | // business logic, such as signIn(). This protocol is implemented by the corresponding 16 | // interactor class. 17 | 18 | func didChangeDetails(_ value: String) 19 | func didMarkAsDone() 20 | func didSave() 21 | func didPrepareView() 22 | func didNavigateBack() 23 | } 24 | 25 | final class ItemViewController: UIViewController, ItemPresentable, ItemViewControllable, UITextFieldDelegate { 26 | 27 | /// The UIKit view representation of this view. 28 | public final var uiviewController: UIViewController { return self } 29 | 30 | weak var listener: ItemPresentableListener? 31 | 32 | @IBOutlet private weak var detailsField: UITextField! 33 | @IBOutlet private weak var doneButton: UIButton! 34 | @IBOutlet private weak var saveButton: UIButton! 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | 39 | listener?.didPrepareView() 40 | } 41 | 42 | @IBAction private func onSaveButtonTap() { 43 | listener?.didChangeDetails(detailsField.text ?? "") 44 | listener?.didSave() 45 | } 46 | 47 | @IBAction private func onDoneButtonTap() { 48 | listener?.didChangeDetails(detailsField.text ?? "") 49 | listener?.didMarkAsDone() 50 | } 51 | 52 | override func viewDidDisappear(_ animated: Bool) { 53 | super.viewDidDisappear(animated) 54 | 55 | if isMovingFromParent { 56 | listener?.didNavigateBack() 57 | } 58 | } 59 | 60 | // MARK: - ItemPresentable 61 | 62 | func showDetails(_ value: String) { 63 | detailsField.text = value 64 | } 65 | 66 | func showTitle(_ title: String) { 67 | self.title = title 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ToDo/RIBs/Item/ItemViewController.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 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /ToDo/RIBs/List/ListBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListBuilder.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol ListDependency: Dependency { 13 | // TODO: Declare the set of dependencies required by this RIB, but cannot be 14 | // created by this RIB. 15 | 16 | var service: ToDoServiceProtocol { get } 17 | var config: Variable { get } 18 | } 19 | 20 | final class ListComponent: Component { 21 | 22 | // TODO: Declare 'fileprivate' dependencies that are only used by this RIB. 23 | 24 | var service: ToDoServiceProtocol { 25 | return dependency.service 26 | } 27 | 28 | var config: Variable { 29 | return dependency.config 30 | } 31 | } 32 | 33 | // MARK: - Builder 34 | 35 | protocol ListBuildable: Buildable { 36 | func build(withListener listener: ListListener) -> (router: ListRouting, actionableItem: ListActionableItem) 37 | } 38 | 39 | final class ListBuilder: Builder, ListBuildable { 40 | 41 | override init(dependency: ListDependency) { 42 | super.init(dependency: dependency) 43 | } 44 | 45 | func build(withListener listener: ListListener) -> (router: ListRouting, actionableItem: ListActionableItem) { 46 | let component = ListComponent(dependency: dependency) 47 | let viewController = ListViewController(nibName: "ListViewController", bundle: Bundle.main) 48 | let itemBuilder = ItemBuilder(dependency: component) 49 | let interactor = ListInteractor(presenter: viewController, 50 | service: component.service, 51 | config: component.config) 52 | interactor.listener = listener 53 | 54 | let router = ListRouter(interactor: interactor, viewController: viewController, 55 | itemBuilder: itemBuilder) 56 | 57 | return (router, interactor) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ToDo/RIBs/List/ListComponent+Item.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListComponent+Item.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | /// The dependencies needed from the parent scope of List to provide for the Item scope. 12 | // TODO: Update ListDependency protocol to inherit this protocol. 13 | protocol ListDependencyItem: Dependency { 14 | // TODO: Declare dependencies needed from the parent scope of List to provide dependencies 15 | // for the Item scope. 16 | } 17 | 18 | extension ListComponent: ItemDependency { 19 | // TODO: Implement properties to provide for Item scope. 20 | } 21 | -------------------------------------------------------------------------------- /ToDo/RIBs/List/ListInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol RevertConfirmationDialogListener: class { 13 | func didSelectRevert(_ item: ItemViewModel) 14 | } 15 | 16 | protocol ListRouting: ViewableRouting { 17 | // TODO: Declare methods the interactor can invoke to manage sub-tree via the router. 18 | 19 | func routeToItem(_ item: ItemViewModel) 20 | func closeItem() 21 | } 22 | 23 | protocol ListPresentable: Presentable, LoadingPresentable { 24 | var listener: ListPresentableListener? { get set } 25 | // TODO: Declare methods the interactor can invoke the presenter to present data. 26 | 27 | func showIncompleteItems(_ items: [ItemViewModel]) 28 | func showCompletedItems(_ items: [ItemViewModel]) 29 | func showTitle(_ title: String) 30 | func showMenu() 31 | 32 | func showRevertConfirmationDialog(item: ItemViewModel, 33 | listener: RevertConfirmationDialogListener) 34 | } 35 | 36 | protocol ListListener: class { 37 | // TODO: Declare methods the interactor can invoke to communicate with other RIBs. 38 | 39 | func listDidAppear() 40 | func didSelectMenu() 41 | } 42 | 43 | final class ListInteractor: PresentableInteractor, ListInteractable, 44 | ListPresentableListener, RevertConfirmationDialogListener, 45 | ListActionableItem { 46 | 47 | weak var router: ListRouting? 48 | weak var listener: ListListener? 49 | 50 | private let service: ToDoServiceProtocol 51 | private let config: Variable 52 | 53 | private var incompleItems: [ItemViewModel] = [] 54 | private var completedItems: [ItemViewModel] = [] 55 | 56 | // TODO: Add additional dependencies to constructor. Do not perform any logic 57 | // in constructor. 58 | init(presenter: ListPresentable, service: ToDoServiceProtocol, config: Variable) { 59 | self.service = service 60 | self.config = config 61 | 62 | super.init(presenter: presenter) 63 | 64 | presenter.listener = self 65 | } 66 | 67 | override func didBecomeActive() { 68 | super.didBecomeActive() 69 | // TODO: Implement business logic here. 70 | 71 | reloadItems() 72 | 73 | presenter.showTitle("ToDo List") 74 | presenter.showMenu() 75 | } 76 | 77 | override func willResignActive() { 78 | super.willResignActive() 79 | // TODO: Pause any business logic. 80 | } 81 | 82 | private func reloadItems() { 83 | presenter.showLoading(with: "Loading...") 84 | 85 | service.getIncompleteItems { [weak self] (response) in 86 | guard let this = self else { return } 87 | 88 | this.presenter.hideLoading() 89 | 90 | switch response { 91 | case .success(let items): 92 | let itemVMs = items.map { ItemViewModel(model: $0) } 93 | 94 | this.incompleItems = itemVMs 95 | 96 | this.presenter.showIncompleteItems(itemVMs) 97 | this.reloadCompletedItems() 98 | case .error(let error): 99 | this.presenter.showError(with: error.message) 100 | } 101 | } 102 | } 103 | 104 | private func reloadCompletedItems() { 105 | presenter.showLoading(with: "Loading...") 106 | 107 | service.getCompletedItems { [weak self] (response) in 108 | guard let this = self else { return } 109 | 110 | this.presenter.hideLoading() 111 | 112 | switch response { 113 | case .success(let items): 114 | let itemVMs = items.map { ItemViewModel(model: $0) } 115 | 116 | this.completedItems = itemVMs 117 | 118 | this.presenter.showCompletedItems(itemVMs) 119 | case .error(let error): 120 | this.presenter.showError(with: error.message) 121 | } 122 | } 123 | } 124 | 125 | // MARK: - ListPresentableListener 126 | 127 | func didAppear() { 128 | listener?.listDidAppear() 129 | } 130 | 131 | func didSelectItem(_ item: ItemViewModel) { 132 | if item.isComplete { 133 | presenter.showRevertConfirmationDialog(item: item, listener: self) 134 | } else { 135 | router?.routeToItem(item) 136 | } 137 | } 138 | 139 | func didSelectMenu() { 140 | listener?.didSelectMenu() 141 | } 142 | 143 | // MARK: - ItemListener 144 | 145 | func closeItem() { 146 | router?.closeItem() 147 | } 148 | 149 | func didUpdateItemDetails(_ item: ItemViewModel) { 150 | router?.closeItem() 151 | 152 | guard let index = incompleItems.index(where: { (model) -> Bool in 153 | return model.key == item.key 154 | }) else { return } 155 | 156 | incompleItems[index] = item 157 | 158 | presenter.showIncompleteItems(incompleItems) 159 | } 160 | 161 | func didMarkAsDone(_ item: ItemViewModel) { 162 | router?.closeItem() 163 | 164 | completedItems.append(item) 165 | 166 | presenter.showCompletedItems(completedItems) 167 | 168 | guard let index = incompleItems.index(where: { (model) -> Bool in 169 | return model.key == item.key 170 | }) else { return } 171 | 172 | incompleItems.remove(at: index) 173 | presenter.showIncompleteItems(incompleItems) 174 | } 175 | 176 | // MARK: - RevertConfirmationDialogListener 177 | 178 | func didSelectRevert(_ item: ItemViewModel) { 179 | let newItem = Item(id: item.key, 180 | createdAt: item.model.createdAt, 181 | details: item.details, 182 | isComplete: false) 183 | 184 | incompleItems.append(ItemViewModel(model: newItem)) 185 | presenter.showIncompleteItems(incompleItems) 186 | 187 | guard let index = completedItems.index(where: { (model) -> Bool in 188 | return model.key == item.key 189 | }) else { return } 190 | 191 | completedItems.remove(at: index) 192 | presenter.showCompletedItems(completedItems) 193 | } 194 | 195 | // MARK: - ListActionableItem 196 | 197 | func createTask(with description: String) -> Observable<(ListActionableItem, ())> { 198 | let newItem = Item(id: Date().timestampInt, createdAt: Date(), details: description, isComplete: false) 199 | 200 | incompleItems.append(ItemViewModel(model: newItem)) 201 | 202 | presenter.showIncompleteItems(incompleItems) 203 | 204 | return Observable.just((self, ())) 205 | } 206 | 207 | func createBlankTask() -> Observable<(ListActionableItem, ())> { 208 | let newItem = Item(id: Date().timestampInt, createdAt: Date(), details: "", isComplete: false) 209 | let itemVM = ItemViewModel(model: newItem) 210 | 211 | incompleItems.append(itemVM) 212 | 213 | presenter.showIncompleteItems(incompleItems) 214 | 215 | router?.routeToItem(itemVM) 216 | 217 | return Observable.just((self, ())) 218 | } 219 | 220 | func doNothing() -> Observable<(ListActionableItem, ())> { 221 | return Observable.just((self, ())) 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /ToDo/RIBs/List/ListRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListRouter.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | protocol ListInteractable: Interactable, ItemListener { 12 | var router: ListRouting? { get set } 13 | var listener: ListListener? { get set } 14 | } 15 | 16 | protocol ListViewControllable: ViewControllable { 17 | // TODO: Declare methods the router invokes to manipulate the view hierarchy. 18 | 19 | func present(view: ViewControllable) 20 | func dismiss(view: ViewControllable) 21 | } 22 | 23 | final class ListRouter: LaunchRouter, ListRouting { 24 | private let itemBuilder: ItemBuildable 25 | private var child: ViewableRouting? 26 | 27 | // TODO: Constructor inject child builder protocols to allow building children. 28 | init(interactor: ListInteractable, viewController: ListViewControllable, 29 | itemBuilder: ItemBuildable) { 30 | 31 | self.itemBuilder = itemBuilder 32 | 33 | super.init(interactor: interactor, viewController: viewController) 34 | 35 | interactor.router = self 36 | } 37 | 38 | // MARK: - ListRouting 39 | 40 | func routeToItem(_ item: ItemViewModel) { 41 | let rib = itemBuilder.build(withListener: interactor, item: item) 42 | 43 | attachChild(rib) 44 | 45 | viewController.present(view: rib.viewControllable) 46 | 47 | child = rib 48 | } 49 | 50 | func closeItem() { 51 | guard let child = self.child else { return } 52 | 53 | detachChild(child) 54 | 55 | viewController.dismiss(view: child.viewControllable) 56 | 57 | self.child = nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ToDo/RIBs/List/ListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewController.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | import UIKit 12 | 13 | protocol ListPresentableListener: class { 14 | // TODO: Declare properties and methods that the view controller can invoke to perform 15 | // business logic, such as signIn(). This protocol is implemented by the corresponding 16 | // interactor class. 17 | 18 | func didSelectItem(_ item: ItemViewModel) 19 | func didSelectMenu() 20 | func didAppear() 21 | } 22 | 23 | final class ListViewController: UIViewController, ListPresentable, ListViewControllable { 24 | 25 | /// The UIKit view representation of this view. 26 | public final var uiviewController: UIViewController { return self } 27 | 28 | weak var listener: ListPresentableListener? 29 | 30 | @IBOutlet private weak var tableView: UITableView! 31 | 32 | private let tableViewCellId = "ListViewControllerItemCellId" 33 | private var incompleteItems: [ItemViewModel] = [] 34 | private var completeItems: [ItemViewModel] = [] 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | 39 | tableView.dataSource = self 40 | tableView.delegate = self 41 | tableView.rowHeight = 60 42 | 43 | tableView.register(UINib(nibName: "ItemCell", bundle: nil), forCellReuseIdentifier: tableViewCellId) 44 | 45 | view.addSubview(tableView) 46 | } 47 | 48 | override func viewDidAppear(_ animated: Bool) { 49 | super.viewDidAppear(animated) 50 | 51 | listener?.didAppear() 52 | } 53 | 54 | @objc 55 | private func onMenuButtonTap() { 56 | listener?.didSelectMenu() 57 | } 58 | 59 | // MARK: - ListPresentable 60 | 61 | func showIncompleteItems(_ items: [ItemViewModel]) { 62 | incompleteItems = items 63 | 64 | tableView.reloadData() 65 | } 66 | 67 | func showCompletedItems(_ items: [ItemViewModel]) { 68 | self.completeItems = items 69 | 70 | tableView.reloadData() 71 | } 72 | 73 | func showTitle(_ title: String) { 74 | self.title = title 75 | } 76 | 77 | func showMenu() { 78 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "More", style: .plain, 79 | target: self, 80 | action: #selector(self.onMenuButtonTap)) 81 | } 82 | 83 | func showRevertConfirmationDialog(item: ItemViewModel, 84 | listener: RevertConfirmationDialogListener) { 85 | 86 | let alert = UIAlertController(title: "Revert changes?", message: nil, preferredStyle: .actionSheet) 87 | 88 | alert.addAction(UIAlertAction(title: "Mark as incomplete", style: .destructive, 89 | handler: { (action) in 90 | listener.didSelectRevert(item) 91 | })) 92 | 93 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 94 | 95 | present(alert, animated: true, completion: nil) 96 | } 97 | 98 | // MARK: - ListViewControllable 99 | 100 | func present(view: ViewControllable) { 101 | navigationController?.pushViewController(view.uiviewController, animated: true) 102 | } 103 | 104 | func dismiss(view: ViewControllable) { 105 | navigationController?.popViewController(animated: true) 106 | } 107 | } 108 | 109 | extension ListViewController: UITableViewDelegate { 110 | public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 111 | tableView.deselectRow(at: indexPath, animated: true) 112 | 113 | if indexPath.section == 0 { 114 | listener?.didSelectItem(incompleteItems[indexPath.row]) 115 | } else { 116 | listener?.didSelectItem(completeItems[indexPath.row]) 117 | } 118 | } 119 | } 120 | 121 | extension ListViewController: UITableViewDataSource { 122 | public func numberOfSections(in tableView: UITableView) -> Int { 123 | return 2 124 | } 125 | 126 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 127 | return section == 0 ? incompleteItems.count : completeItems.count 128 | } 129 | 130 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 131 | guard let cell = tableView.dequeueReusableCell(withIdentifier: tableViewCellId) else { fatalError() } 132 | guard let itemCell = cell as? ItemCell else { fatalError() } 133 | 134 | if indexPath.section == 0 { 135 | itemCell.display(item: incompleteItems[indexPath.row]) 136 | } else { 137 | itemCell.display(item: completeItems[indexPath.row]) 138 | } 139 | 140 | return cell 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ToDo/RIBs/List/ListViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedIn/LoggedInBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedInBuilder.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol LoggedInDependency: Dependency { 13 | var loggedInViewController: LoggedInViewControllable { get } 14 | 15 | var service: ToDoServiceProtocol { get } 16 | var config: Variable { get } 17 | } 18 | 19 | final class LoggedInComponent: Component { 20 | let sessionToken: String 21 | 22 | fileprivate var loggedInViewController: LoggedInViewControllable { 23 | return dependency.loggedInViewController 24 | } 25 | 26 | var service: ToDoServiceProtocol { 27 | return dependency.service 28 | } 29 | 30 | var config: Variable { 31 | return dependency.config 32 | } 33 | 34 | init(dependency: LoggedInDependency, sessionToken: String) { 35 | self.sessionToken = sessionToken 36 | 37 | super.init(dependency: dependency) 38 | } 39 | } 40 | 41 | // MARK: - Builder 42 | 43 | protocol LoggedInBuildable: Buildable { 44 | func build(withListener listener: LoggedInListener, session: String) -> 45 | (router: LoggedInRouting, actionableItem: LoggedInActionableItem) 46 | } 47 | 48 | final class LoggedInBuilder: Builder, LoggedInBuildable { 49 | 50 | override init(dependency: LoggedInDependency) { 51 | super.init(dependency: dependency) 52 | } 53 | 54 | func build(withListener listener: LoggedInListener, session: String) -> 55 | (router: LoggedInRouting, actionableItem: LoggedInActionableItem) { 56 | 57 | let component = LoggedInComponent(dependency: dependency, sessionToken: session) 58 | let interactor = LoggedInInteractor(sessionToken: session) 59 | let drawerBuilder = DrawerBuilder(dependency: component) 60 | 61 | interactor.listener = listener 62 | 63 | let router = LoggedInRouter(interactor: interactor, 64 | viewController: component.loggedInViewController, 65 | drawerBuilder: drawerBuilder) 66 | return (router, interactor) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedIn/LoggedInComponent+Drawer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedInComponent+Drawer.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | /// The dependencies needed from the parent scope of Root to provide for the LoggedOut scope. 12 | // TODO: Update RootDependency protocol to inherit this protocol. 13 | protocol LoggedInDependencyDrawer: Dependency { 14 | 15 | // TODO: Declare dependencies needed from the parent scope of LoggedIn to provide dependencies 16 | // for the child scope. 17 | } 18 | 19 | extension LoggedInComponent: DrawerDependency { 20 | // TODO: Implement properties to provide for AccountSelection scope. 21 | } 22 | 23 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedIn/LoggedInInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedInInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol LoggedInRouting: Routing { 13 | func cleanupViews() 14 | func routeToDrawer() -> DrawerActionableItem 15 | } 16 | 17 | protocol LoggedInListener: class { 18 | // TODO: Declare methods the interactor can invoke to communicate with other RIBs. 19 | func didFinishLoggedInJob() 20 | } 21 | 22 | final class LoggedInInteractor: Interactor, LoggedInInteractable, LoggedInActionableItem { 23 | weak var router: LoggedInRouting? 24 | weak var listener: LoggedInListener? 25 | 26 | var drawerActionableItem: DrawerActionableItem? 27 | 28 | private let sessionToken: String 29 | 30 | init(sessionToken: String) { 31 | self.sessionToken = sessionToken 32 | 33 | super.init() 34 | } 35 | 36 | override func didBecomeActive() { 37 | super.didBecomeActive() 38 | 39 | drawerActionableItem = router?.routeToDrawer() 40 | } 41 | 42 | override func willResignActive() { 43 | super.willResignActive() 44 | 45 | // TODO: Pause any business logic. 46 | 47 | router?.cleanupViews() 48 | } 49 | 50 | // MARK: - DrawerListener 51 | 52 | func drawerDidAppear() { 53 | if let drawerActionableItem = drawerActionableItem { 54 | drawerActionableItemSubject.onNext(drawerActionableItem) 55 | } 56 | 57 | drawerActionableItem = nil 58 | } 59 | 60 | // MARK: - LoggedInActionableItem 61 | 62 | func waitForDrawer() -> Observable<(DrawerActionableItem, ())> { 63 | return drawerActionableItemSubject 64 | .map { (drawerItem: DrawerActionableItem) -> (DrawerActionableItem, ()) in 65 | (drawerItem, ()) 66 | } 67 | } 68 | 69 | // MARK: - Private 70 | 71 | private let drawerActionableItemSubject = ReplaySubject.create(bufferSize: 1) 72 | } 73 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedIn/LoggedInRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedInRouter.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol LoggedInInteractable: Interactable, DrawerListener { 13 | var router: LoggedInRouting? { get set } 14 | var listener: LoggedInListener? { get set } 15 | } 16 | 17 | protocol LoggedInViewControllable: ViewControllable { 18 | func replaceModal(viewController: ViewControllable?) 19 | } 20 | 21 | final class LoggedInRouter: Router, LoggedInRouting { 22 | private let viewController: LoggedInViewControllable 23 | private let drawerBuilder: DrawerBuildable 24 | private var drawerChild: (router: DrawerRouting, actionableItem: DrawerActionableItem)? 25 | 26 | required init(interactor: LoggedInInteractable, 27 | viewController: LoggedInViewControllable, 28 | drawerBuilder: DrawerBuildable) { 29 | 30 | self.drawerBuilder = drawerBuilder 31 | self.viewController = viewController 32 | 33 | super.init(interactor: interactor) 34 | 35 | interactor.router = self 36 | } 37 | 38 | // MARK: - LoggedInRouting 39 | 40 | func cleanupViews() { 41 | if drawerChild != nil { 42 | viewController.replaceModal(viewController: nil) 43 | } 44 | } 45 | 46 | func routeToDrawer() -> DrawerActionableItem { 47 | let rib = drawerBuilder.build(withListener: interactor) 48 | 49 | self.drawerChild = rib 50 | 51 | attachChild(rib.router) 52 | 53 | viewController.replaceModal(viewController: rib.router.viewControllable) 54 | 55 | return rib.actionableItem 56 | } 57 | 58 | // MARK: - Private 59 | 60 | private func detachCurrentChild() { 61 | if let child = drawerChild { 62 | detachChild(child.router) 63 | viewController.replaceModal(viewController: nil) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedOut/LoggedOutBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedOutBuilder.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol LoggedOutDependency: Dependency { 13 | // TODO: Declare the set of dependencies required by this RIB, but cannot be 14 | // created by this RIB. 15 | 16 | var service: ToDoServiceProtocol { get } 17 | var config: Variable { get } 18 | } 19 | 20 | final class LoggedOutComponent: Component { 21 | 22 | // TODO: Declare 'fileprivate' dependencies that are only used by this RIB. 23 | 24 | var service: ToDoServiceProtocol { 25 | return dependency.service 26 | } 27 | 28 | var config: Variable { 29 | return dependency.config 30 | } 31 | } 32 | 33 | // MARK: - Builder 34 | 35 | protocol LoggedOutBuildable: Buildable { 36 | func build(withListener listener: LoggedOutListener) -> LoggedOutRouting 37 | } 38 | 39 | final class LoggedOutBuilder: Builder, LoggedOutBuildable { 40 | 41 | override init(dependency: LoggedOutDependency) { 42 | super.init(dependency: dependency) 43 | } 44 | 45 | func build(withListener listener: LoggedOutListener) -> LoggedOutRouting { 46 | let component = LoggedOutComponent(dependency: dependency) 47 | let viewController = LoggedOutViewController(nibName: "LoggedOutViewController", bundle: Bundle.main) 48 | let interactor = LoggedOutInteractor(presenter: viewController, 49 | service: component.service, 50 | config: component.config) 51 | interactor.listener = listener 52 | 53 | return LoggedOutRouter(interactor: interactor, viewController: viewController) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedOut/LoggedOutInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedOutInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol LoggedOutRouting: ViewableRouting { 13 | // TODO: Declare methods the interactor can invoke to manage sub-tree via the router. 14 | } 15 | 16 | protocol LoggedOutPresentable: Presentable { 17 | var listener: LoggedOutPresentableListener? { get set } 18 | // TODO: Declare methods the interactor can invoke the presenter to present data. 19 | 20 | func showLoadingIndicator() 21 | func hideLoadingIndicator() 22 | func showError(_ text: String) 23 | } 24 | 25 | protocol LoggedOutListener: class { 26 | func didLogin(sessionToken: String) 27 | } 28 | 29 | final class LoggedOutInteractor: PresentableInteractor, LoggedOutInteractable, 30 | LoggedOutPresentableListener { 31 | 32 | weak var router: LoggedOutRouting? 33 | weak var listener: LoggedOutListener? 34 | 35 | private let service: ToDoServiceProtocol 36 | private let config: Variable 37 | 38 | // TODO: Add additional dependencies to constructor. Do not perform any logic 39 | // in constructor. 40 | init(presenter: LoggedOutPresentable, service: ToDoServiceProtocol, config: Variable) { 41 | self.service = service 42 | self.config = config 43 | 44 | super.init(presenter: presenter) 45 | 46 | presenter.listener = self 47 | } 48 | 49 | override func didBecomeActive() { 50 | super.didBecomeActive() 51 | // TODO: Implement business logic here. 52 | } 53 | 54 | override func willResignActive() { 55 | super.willResignActive() 56 | // TODO: Pause any business logic. 57 | } 58 | 59 | // MARK: - LoggedOutPresentableListener 60 | 61 | func login(withName userName: String?, password: String?) { 62 | guard let name = userName, let pass = password else { return } 63 | 64 | if name.isEmpty || pass.isEmpty { 65 | presenter.showError("Missing username or password") 66 | 67 | return 68 | } 69 | 70 | presenter.showLoadingIndicator() 71 | 72 | // TODO: Here should be a service call to request an access token 73 | service.requestAccessToken(login: name, password: pass) { [weak self] (response) in 74 | guard let this = self else { return } 75 | 76 | this.presenter.hideLoadingIndicator() 77 | 78 | switch response { 79 | case .success(let token): 80 | this.listener?.didLogin(sessionToken: token) 81 | default: 82 | break 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedOut/LoggedOutRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedOutRouter.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | protocol LoggedOutInteractable: Interactable { 12 | var router: LoggedOutRouting? { get set } 13 | var listener: LoggedOutListener? { get set } 14 | } 15 | 16 | protocol LoggedOutViewControllable: ViewControllable { 17 | // TODO: Declare methods the router invokes to manipulate the view hierarchy. 18 | } 19 | 20 | final class LoggedOutRouter: ViewableRouter, LoggedOutRouting { 21 | 22 | // TODO: Constructor inject child builder protocols to allow building children. 23 | override init(interactor: LoggedOutInteractable, viewController: LoggedOutViewControllable) { 24 | super.init(interactor: interactor, viewController: viewController) 25 | 26 | interactor.router = self 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedOut/LoggedOutViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggedOutViewController.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxCocoa 11 | import RxSwift 12 | import UIKit 13 | import TransitionButton 14 | import SVProgressHUD 15 | 16 | protocol LoggedOutPresentableListener: class { 17 | func login(withName: String?, password: String?) 18 | } 19 | 20 | final class LoggedOutViewController: UIViewController, LoggedOutPresentable, LoggedOutViewControllable { 21 | // MARK: - Private 22 | 23 | private let disposeBag = DisposeBag() 24 | 25 | @IBOutlet private var loginButton: TransitionButton! 26 | @IBOutlet private var usernameField: UITextField! 27 | @IBOutlet private var passwordField: UITextField! 28 | 29 | weak var listener: LoggedOutPresentableListener? 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | 34 | loginButton.rx.tap 35 | .debounce(0.5, scheduler: MainScheduler.instance) 36 | .subscribe(onNext: { [weak self] in 37 | guard let this = self else { return } 38 | 39 | this.usernameField.resignFirstResponder() 40 | this.passwordField.resignFirstResponder() 41 | 42 | this.listener?.login(withName: this.usernameField?.text, password: this.passwordField?.text) 43 | }) 44 | .disposed(by: disposeBag) 45 | } 46 | 47 | // MARK: - LoggedOutPresentable 48 | 49 | func showLoadingIndicator() { 50 | loginButton.startAnimation() 51 | } 52 | 53 | func hideLoadingIndicator() { 54 | loginButton.stopAnimation() 55 | } 56 | 57 | func showError(_ text: String) { 58 | SVProgressHUD.showError(withStatus: text) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ToDo/RIBs/LoggedOut/LoggedOutViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /ToDo/RIBs/Menu/MenuBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuBuilder.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 2/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol MenuDependency: Dependency { 13 | // TODO: Declare the set of dependencies required by this RIB, but cannot be 14 | // created by this RIB. 15 | 16 | var service: ToDoServiceProtocol { get } 17 | var config: Variable { get } 18 | } 19 | 20 | final class MenuComponent: Component { 21 | 22 | // TODO: Declare 'fileprivate' dependencies that are only used by this RIB. 23 | 24 | var service: ToDoServiceProtocol { 25 | return dependency.service 26 | } 27 | 28 | var config: Variable { 29 | return dependency.config 30 | } 31 | } 32 | 33 | // MARK: - Builder 34 | 35 | protocol MenuBuildable: Buildable { 36 | func build(withListener listener: MenuListener) -> MenuRouting 37 | } 38 | 39 | final class MenuBuilder: Builder, MenuBuildable { 40 | 41 | override init(dependency: MenuDependency) { 42 | super.init(dependency: dependency) 43 | } 44 | 45 | func build(withListener listener: MenuListener) -> MenuRouting { 46 | let component = MenuComponent(dependency: dependency) 47 | let viewController = MenuViewController(nibName: "MenuViewController", bundle: Bundle.main) 48 | let interactor = MenuInteractor(presenter: viewController, 49 | service: component.service, 50 | config: component.config) 51 | interactor.listener = listener 52 | 53 | return MenuRouter(interactor: interactor, viewController: viewController) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ToDo/RIBs/Menu/MenuInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 2/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | import Eureka 12 | 13 | protocol MenuRouting: ViewableRouting { 14 | // TODO: Declare methods the interactor can invoke to manage sub-tree via the router. 15 | } 16 | 17 | protocol MenuPresentable: Presentable { 18 | var listener: MenuPresentableListener? { get set } 19 | // TODO: Declare methods the interactor can invoke the presenter to present data. 20 | 21 | func showMenuItems(_ sections: [Section]) 22 | } 23 | 24 | protocol MenuListener: class { 25 | // TODO: Declare methods the interactor can invoke to communicate with other RIBs. 26 | 27 | func didSelectMenu(_ menuItem: String) 28 | } 29 | 30 | final class MenuInteractor: PresentableInteractor, MenuInteractable, MenuPresentableListener { 31 | 32 | weak var router: MenuRouting? 33 | weak var listener: MenuListener? 34 | 35 | private let service: ToDoServiceProtocol 36 | private let config: Variable 37 | 38 | // TODO: Add additional dependencies to constructor. Do not perform any logic 39 | // in constructor. 40 | init(presenter: MenuPresentable, service: ToDoServiceProtocol, config: Variable) { 41 | self.service = service 42 | self.config = config 43 | 44 | super.init(presenter: presenter) 45 | presenter.listener = self 46 | } 47 | 48 | override func didBecomeActive() { 49 | super.didBecomeActive() 50 | // TODO: Implement business logic here. 51 | } 52 | 53 | override func willResignActive() { 54 | super.willResignActive() 55 | // TODO: Pause any business logic. 56 | } 57 | 58 | // MARK: - MenuPresentableListener 59 | 60 | func didPrepareView() { 61 | var form: [Section] = [] 62 | var section = Section("Support") 63 | 64 | form.append(section) 65 | 66 | var buttonRow = ButtonRow(tag: "feedback") 67 | 68 | buttonRow.title = "Feedback" 69 | 70 | buttonRow.onCellSelection { [unowned self] (cell, row) in 71 | self.onFeedbackTap() 72 | } 73 | 74 | buttonRow.cellUpdate { (cell, row) in 75 | cell.tintColor = .darkGray 76 | cell.accessoryType = .disclosureIndicator 77 | cell.textLabel?.textAlignment = .left 78 | } 79 | 80 | section.append(buttonRow) 81 | 82 | buttonRow = ButtonRow(tag: "share") 83 | 84 | buttonRow.title = "Share" 85 | 86 | buttonRow.onCellSelection { [unowned self] (cell, row) in 87 | self.onShareTap() 88 | } 89 | 90 | buttonRow.cellUpdate { (cell, row) in 91 | cell.tintColor = .darkGray 92 | cell.accessoryType = .disclosureIndicator 93 | cell.textLabel?.textAlignment = .left 94 | } 95 | 96 | section.append(buttonRow) 97 | 98 | buttonRow = ButtonRow(tag: "rate") 99 | 100 | buttonRow.title = "Rate us" 101 | 102 | buttonRow.onCellSelection { [unowned self] (cell, row) in 103 | self.onRateTap() 104 | } 105 | 106 | buttonRow.cellUpdate { (cell, row) in 107 | cell.tintColor = .darkGray 108 | cell.accessoryType = .disclosureIndicator 109 | cell.textLabel?.textAlignment = .left 110 | } 111 | 112 | section.append(buttonRow) 113 | 114 | section = Section("Extra") 115 | 116 | form.append(section) 117 | 118 | buttonRow = ButtonRow(tag: "tos") 119 | 120 | buttonRow.title = "Terms and Conditions" 121 | 122 | buttonRow.onCellSelection { [unowned self] (cell, row) in 123 | self.onTermsOfServiceTap() 124 | } 125 | 126 | buttonRow.cellUpdate { (cell, row) in 127 | cell.tintColor = .darkGray 128 | cell.accessoryType = .disclosureIndicator 129 | cell.textLabel?.textAlignment = .left 130 | } 131 | 132 | section.append(buttonRow) 133 | 134 | buttonRow = ButtonRow(tag: "pp") 135 | 136 | buttonRow.title = "Privacy Policy" 137 | 138 | buttonRow.onCellSelection { [unowned self] (cell, row) in 139 | self.onPrivacyPolicyTap() 140 | } 141 | 142 | buttonRow.cellUpdate { (cell, row) in 143 | cell.tintColor = .darkGray 144 | cell.accessoryType = .disclosureIndicator 145 | cell.textLabel?.textAlignment = .left 146 | } 147 | 148 | section.append(buttonRow) 149 | 150 | section = Section() 151 | 152 | form.append(section) 153 | 154 | let labelRow = LabelRow(tag: "version") 155 | 156 | labelRow.title = "Version" 157 | labelRow.value = "\(config.value.appVersion) (\(config.value.buildNumber))" 158 | 159 | section.append(labelRow) 160 | 161 | presenter.showMenuItems(form) 162 | } 163 | 164 | private func onFeedbackTap() { 165 | listener?.didSelectMenu("Feedback") 166 | } 167 | 168 | private func onShareTap() { 169 | listener?.didSelectMenu("Share") 170 | } 171 | 172 | private func onRateTap() { 173 | listener?.didSelectMenu("Rate us") 174 | } 175 | 176 | private func onTermsOfServiceTap() { 177 | listener?.didSelectMenu("Terms of Service") 178 | } 179 | 180 | private func onPrivacyPolicyTap() { 181 | listener?.didSelectMenu("Privacy Policy") 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /ToDo/RIBs/Menu/MenuRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuRouter.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 2/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | protocol MenuInteractable: Interactable { 12 | var router: MenuRouting? { get set } 13 | var listener: MenuListener? { get set } 14 | } 15 | 16 | protocol MenuViewControllable: ViewControllable { 17 | // TODO: Declare methods the router invokes to manipulate the view hierarchy. 18 | } 19 | 20 | final class MenuRouter: ViewableRouter, MenuRouting { 21 | 22 | // TODO: Constructor inject child builder protocols to allow building children. 23 | override init(interactor: MenuInteractable, viewController: MenuViewControllable) { 24 | super.init(interactor: interactor, viewController: viewController) 25 | interactor.router = self 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ToDo/RIBs/Menu/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuViewController.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 2/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | import UIKit 12 | import Eureka 13 | 14 | protocol MenuPresentableListener: class { 15 | // TODO: Declare properties and methods that the view controller can invoke to perform 16 | // business logic, such as signIn(). This protocol is implemented by the corresponding 17 | // interactor class. 18 | 19 | func didPrepareView() 20 | } 21 | 22 | final class MenuViewController: FormViewController, MenuPresentable, MenuViewControllable { 23 | /// The UIKit view representation of this view. 24 | public final var uiviewController: UIViewController { return self } 25 | 26 | weak var listener: MenuPresentableListener? 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | 31 | listener?.didPrepareView() 32 | } 33 | 34 | // MARK: - MenuPresentable 35 | 36 | func showMenuItems(_ sections: [Section]) { 37 | LabelRow.defaultCellUpdate = { cell, row in cell.tintColor = .darkGray } 38 | URLRow.defaultCellUpdate = { cell, row in cell.textField.textColor = .darkGray } 39 | CheckRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 40 | SegmentedRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 41 | PasswordRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 42 | DateRow.defaultRowInitializer = { row in row.minimumDate = Date() } 43 | NameRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 44 | TextAreaRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 45 | EmailRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 46 | PhoneRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 47 | SwitchRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 48 | TimeInlineRow.defaultCellSetup = { cell, row in cell.tintColor = .darkGray } 49 | ButtonRow.defaultCellSetup = { cell, row in 50 | cell.tintColor = .darkGray 51 | cell.accessoryType = .disclosureIndicator 52 | cell.textLabel?.textAlignment = .left 53 | } 54 | 55 | form.append(contentsOf: sections) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ToDo/RIBs/Menu/MenuViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ToDo/RIBs/Root/RootBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootBuilder.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol RootDependency: Dependency { 13 | // TODO: Declare the set of dependencies required by this RIB, but cannot be 14 | // created by this RIB. 15 | 16 | var application: UIApplication { get } 17 | var launchOptions: [UIApplication.LaunchOptionsKey: Any]? { get } 18 | var service: ToDoServiceProtocol { get } 19 | var config: Variable { get } 20 | } 21 | 22 | final class RootComponent: Component { 23 | // TODO: Declare 'fileprivate' dependencies that are only used by this RIB. 24 | 25 | let rootViewController: RootViewController 26 | 27 | var application: UIApplication { 28 | return dependency.application 29 | } 30 | 31 | var launchOptions: [UIApplication.LaunchOptionsKey: Any]? { 32 | return dependency.launchOptions 33 | } 34 | 35 | var config: Variable { 36 | return dependency.config 37 | } 38 | 39 | var service: ToDoServiceProtocol { 40 | return dependency.service 41 | } 42 | 43 | init(dependency: RootDependency, 44 | rootViewController: RootViewController) { 45 | 46 | self.rootViewController = rootViewController 47 | 48 | super.init(dependency: dependency) 49 | } 50 | } 51 | 52 | // MARK: - Builder 53 | 54 | protocol RootBuildable: Buildable { 55 | func build() -> (launchRouter: LaunchRouting, urlHandler: UrlHandler) 56 | } 57 | 58 | final class RootBuilder: Builder, RootBuildable { 59 | 60 | override init(dependency: RootDependency) { 61 | super.init(dependency: dependency) 62 | } 63 | 64 | func build() -> (launchRouter: LaunchRouting, urlHandler: UrlHandler) { 65 | let viewController = RootViewController() 66 | let component = RootComponent(dependency: dependency, 67 | rootViewController: viewController) 68 | let interactor = RootInteractor(presenter: viewController) 69 | 70 | let loggedOutBuilder = LoggedOutBuilder(dependency: component) 71 | let loggedInBuilder = LoggedInBuilder(dependency: component) 72 | 73 | let router = RootRouter(interactor: interactor, 74 | viewController: viewController, 75 | loggedOutBuilder: loggedOutBuilder, 76 | loggedInBuilder: loggedInBuilder) 77 | 78 | return (router, interactor) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ToDo/RIBs/Root/RootComponent+LoggedIn.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootComponent+LoggedIn.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | /// The dependencies needed from the parent scope of Root to provide for the LoggedIn scope. 12 | // TODO: Update RootDependency protocol to inherit this protocol. 13 | protocol RootDependencyLoggedIn: Dependency { 14 | 15 | // TODO: Declare dependencies needed from the parent scope of Root to provide dependencies 16 | // for the LoggedIn scope. 17 | } 18 | 19 | extension RootComponent: LoggedInDependency { 20 | var loggedInViewController: LoggedInViewControllable { 21 | return rootViewController 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ToDo/RIBs/Root/RootComponent+LoggedOut.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootComponent+LoggedOut.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | /// The dependencies needed from the parent scope of Root to provide for the LoggedOut scope. 12 | // TODO: Update RootDependency protocol to inherit this protocol. 13 | protocol RootDependencyLoggedOut: Dependency { 14 | 15 | // TODO: Declare dependencies needed from the parent scope of Root to provide dependencies 16 | // for the LoggedOut scope. 17 | } 18 | 19 | extension RootComponent: LoggedOutDependency { 20 | // TODO: Implement properties to provide for LoggedOut scope. 21 | } 22 | -------------------------------------------------------------------------------- /ToDo/RIBs/Root/RootInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootInteractor.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | protocol RootRouting: ViewableRouting { 13 | func routeToLoggedIn(sessionToken: String) -> LoggedInActionableItem 14 | func routeToLoggedOut() 15 | } 16 | 17 | protocol RootPresentable: Presentable { 18 | var listener: RootPresentableListener? { get set } 19 | // TODO: Declare methods the interactor can invoke the presenter to present data. 20 | } 21 | 22 | protocol RootListener: class { 23 | // TODO: Declare methods the interactor can invoke to communicate with other RIBs. 24 | } 25 | 26 | final class RootInteractor: PresentableInteractor, RootInteractable, RootPresentableListener, 27 | RootActionableItem, UrlHandler { 28 | 29 | weak var router: RootRouting? 30 | weak var listener: RootListener? 31 | 32 | private var isFirstTimeLaunch: Bool = true 33 | 34 | // TODO: Add additional dependencies to constructor. Do not perform any logic 35 | // in constructor. 36 | override init(presenter: RootPresentable) { 37 | super.init(presenter: presenter) 38 | 39 | presenter.listener = self 40 | } 41 | 42 | override func didBecomeActive() { 43 | super.didBecomeActive() 44 | // TODO: Implement business logic here. 45 | } 46 | 47 | override func willResignActive() { 48 | super.willResignActive() 49 | // TODO: Pause any business logic. 50 | } 51 | 52 | private func nextAction(with session: String, animated: Bool) { 53 | let loggedInActionableItem = router?.routeToLoggedIn(sessionToken: session) 54 | 55 | if let loggedInActionableItem = loggedInActionableItem { 56 | loggedInActionableItemSubject.onNext(loggedInActionableItem) 57 | } 58 | } 59 | 60 | // MARK: - RootPresentableListener 61 | 62 | func viewDidAppear() { 63 | if isFirstTimeLaunch { 64 | isFirstTimeLaunch = false 65 | 66 | router?.routeToLoggedOut() 67 | } 68 | } 69 | 70 | // MARK: - LoggedOutListener 71 | 72 | func didLogin(sessionToken: String) { 73 | let loggedInActionableItem = router?.routeToLoggedIn(sessionToken: sessionToken) 74 | 75 | if let loggedInActionableItem = loggedInActionableItem { 76 | loggedInActionableItemSubject.onNext(loggedInActionableItem) 77 | } 78 | } 79 | 80 | // MARK: - LoggedInListener 81 | 82 | func didFinishLoggedInJob() { 83 | router?.routeToLoggedOut() 84 | } 85 | 86 | // MARK: - UrlHandler 87 | 88 | func handle(_ url: URL) -> Bool{ 89 | let deeplinkFlow = DeeplinkWorkflow(url: url) 90 | 91 | deeplinkFlow.subscribe(self).disposeOnDeactivate(interactor: self) 92 | 93 | return true 94 | } 95 | 96 | // MARK: - RootActionableItem 97 | 98 | func waitForLogin() -> Observable<(LoggedInActionableItem, ())> { 99 | return loggedInActionableItemSubject 100 | .map { (loggedInItem: LoggedInActionableItem) -> (LoggedInActionableItem, ()) in 101 | (loggedInItem, ()) 102 | } 103 | } 104 | 105 | // MARK: - Private 106 | 107 | private let loggedInActionableItemSubject = ReplaySubject.create(bufferSize: 1) 108 | } 109 | 110 | -------------------------------------------------------------------------------- /ToDo/RIBs/Root/RootRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootRouter.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | protocol RootInteractable: Interactable, LoggedOutListener, LoggedInListener { 12 | var router: RootRouting? { get set } 13 | var listener: RootListener? { get set } 14 | } 15 | 16 | protocol RootViewControllable: ViewControllable { 17 | func replaceModal(viewController: ViewControllable?) 18 | } 19 | 20 | final class RootRouter: LaunchRouter, RootRouting { 21 | // MARK: - Private properties 22 | 23 | private let loggedOutBuilder: LoggedOutBuildable 24 | private let loggedInBuilder: LoggedInBuildable 25 | 26 | private var loggedIn: (router: LoggedInRouting, actionableItem: LoggedInActionableItem)? 27 | private var loggedOut: ViewableRouting? 28 | 29 | init(interactor: RootInteractable, 30 | viewController: RootViewControllable, 31 | loggedOutBuilder: LoggedOutBuildable, 32 | loggedInBuilder: LoggedInBuildable) { 33 | 34 | self.loggedOutBuilder = loggedOutBuilder 35 | self.loggedInBuilder = loggedInBuilder 36 | 37 | super.init(interactor: interactor, viewController: viewController) 38 | 39 | interactor.router = self 40 | } 41 | 42 | override func didLoad() { 43 | super.didLoad() 44 | 45 | // If need to start with login screen: 46 | //routeToLoggedOut() 47 | } 48 | 49 | func routeToLoggedIn(sessionToken: String) -> LoggedInActionableItem { 50 | // Detach logged out. 51 | if let child = self.loggedOut { 52 | detachChild(child) 53 | viewController.replaceModal(viewController: nil) 54 | 55 | self.loggedOut = nil 56 | } 57 | 58 | loggedIn = loggedInBuilder.build(withListener: interactor, session: sessionToken) 59 | 60 | guard let loggedIn = self.loggedIn else { fatalError("failed to allocate rib") } 61 | 62 | attachChild(loggedIn.router) 63 | 64 | return loggedIn.actionableItem 65 | } 66 | 67 | func routeToLoggedOut() { 68 | // Detach loggedIn out. 69 | if let child = loggedIn { 70 | detachChild(child.router) 71 | } 72 | 73 | if loggedOut == nil { 74 | loggedOut = loggedOutBuilder.build(withListener: interactor) 75 | } 76 | 77 | guard let loggedOut = self.loggedOut else { fatalError("failed to allocate rib") } 78 | 79 | attachChild(loggedOut) 80 | viewController.replaceModal(viewController: loggedOut.viewControllable) 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /ToDo/RIBs/Root/RootViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewController.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import SnapKit 11 | import UIKit 12 | 13 | protocol RootPresentableListener: class { 14 | func viewDidAppear() 15 | } 16 | 17 | final class RootViewController: UIViewController, RootPresentable, RootViewControllable { 18 | 19 | weak var listener: RootPresentableListener? 20 | 21 | init() { 22 | super.init(nibName: nil, bundle: nil) 23 | } 24 | 25 | required init?(coder aDecoder: NSCoder) { 26 | fatalError("Method is not supported") 27 | } 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | view.backgroundColor = UIColor.white 33 | } 34 | 35 | override func viewDidAppear(_ animated: Bool) { 36 | super.viewDidAppear(animated) 37 | 38 | listener?.viewDidAppear() 39 | } 40 | 41 | // MARK: - RootViewControllable 42 | 43 | func replaceModal(viewController: ViewControllable?) { 44 | targetViewController = viewController 45 | 46 | guard !animationInProgress else { return } 47 | 48 | if presentedViewController != nil { 49 | animationInProgress = true 50 | dismiss(animated: true) { [weak self] in 51 | guard let this = self else { return } 52 | 53 | if this.targetViewController != nil { 54 | this.presentTargetViewController() 55 | } else { 56 | this.animationInProgress = false 57 | } 58 | } 59 | } else { 60 | presentTargetViewController() 61 | } 62 | } 63 | 64 | // MARK: - Private 65 | 66 | private var targetViewController: ViewControllable? 67 | private var animationInProgress = false 68 | 69 | private func presentTargetViewController() { 70 | if let targetViewController = targetViewController { 71 | animationInProgress = true 72 | present(targetViewController.uiviewController, animated: true) { [weak self] in 73 | self?.animationInProgress = false 74 | } 75 | } 76 | } 77 | } 78 | 79 | // MARK: LoggedInViewControllable 80 | 81 | extension RootViewController: LoggedInViewControllable { 82 | } 83 | -------------------------------------------------------------------------------- /ToDo/RIBs/Workflows/DeeplinkWorkflow/DeeplinkWorkflow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeeplinkWorkflow.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | import RxSwift 11 | 12 | public class DeeplinkWorkflow: Workflow { 13 | enum FlowType { 14 | case task(String) 15 | case blankTask 16 | case hi 17 | } 18 | 19 | public init(url: URL) { 20 | super.init() 21 | 22 | let flowType = parseDeeplinkData(from: url) 23 | 24 | self 25 | .onStep { (rootItem: RootActionableItem) -> Observable<(LoggedInActionableItem, ())> in 26 | rootItem.waitForLogin() 27 | } 28 | .onStep { (loggedInItem: LoggedInActionableItem, _) -> Observable<(DrawerActionableItem, ())> in 29 | loggedInItem.waitForDrawer() 30 | } 31 | .onStep { (drawerItem: DrawerActionableItem, _) -> Observable<(ListActionableItem, ())> in 32 | switch flowType { 33 | case .hi: 34 | return drawerItem.sayHi() 35 | default: 36 | return drawerItem.waitForList() 37 | } 38 | } 39 | .onStep { (listItem: ListActionableItem, _) -> Observable<(ListActionableItem, ())> in 40 | switch flowType { 41 | case .blankTask: 42 | return listItem.createBlankTask() 43 | case .task(let details): 44 | return listItem.createTask(with: details) 45 | case .hi: 46 | return listItem.doNothing() 47 | } 48 | } 49 | .commit() 50 | } 51 | 52 | private func parseDeeplinkData(from url: URL) -> FlowType { 53 | guard let host = url.host else { return .blankTask } 54 | 55 | let value = url.path.replacingOccurrences(of: "/", with: "") 56 | 57 | if host == "task" { 58 | return .task(value) 59 | } else if host == "sayhi" { 60 | return .hi 61 | } 62 | 63 | return .blankTask 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /ToDo/RIBs/Workflows/RootWorkflow/RootWorkflow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootWorkflow.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import RIBs 10 | 11 | public typealias RootWorkflow = Workflow 12 | -------------------------------------------------------------------------------- /ToDo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /ToDo/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 | -------------------------------------------------------------------------------- /ToDo/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleURLTypes 20 | 21 | 22 | CFBundleTypeRole 23 | Editor 24 | CFBundleURLName 25 | au.com.dev4jam-todo-ios 26 | CFBundleURLSchemes 27 | 28 | todo 29 | 30 | 31 | 32 | CFBundleVersion 33 | 1 34 | LSRequiresIPhoneOS 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ToDo/Services/ToDoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToDoService.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ToDoServiceError: Error { 12 | case failedToGetItems(message: String) 13 | case failedToGetItemDetails(message: String) 14 | 15 | var message: String { 16 | switch self { 17 | case .failedToGetItems(let message), 18 | .failedToGetItemDetails(let message): 19 | 20 | return message 21 | } 22 | } 23 | } 24 | 25 | enum ToDoServiceListResponse { 26 | case error(ToDoServiceError) 27 | case success(items: [Item]) 28 | } 29 | 30 | enum ToDoServiceAuthResponse { 31 | case error(ToDoServiceError) 32 | case success(sessionToken: String) 33 | } 34 | 35 | typealias ToDoServiceItemsCallback = ((ToDoServiceListResponse) -> Void) 36 | typealias ToDoServiceAuthCallback = ((ToDoServiceAuthResponse) -> Void) 37 | 38 | protocol ToDoServiceProtocol: class { 39 | func requestAccessToken(login: String, password: String, callback: @escaping ToDoServiceAuthCallback) 40 | func getAllItems(callback: @escaping ToDoServiceItemsCallback) 41 | func getIncompleteItems(callback: @escaping ToDoServiceItemsCallback) 42 | func getCompletedItems(callback: @escaping ToDoServiceItemsCallback) 43 | } 44 | 45 | final class ToDoService: ToDoServiceProtocol { 46 | private let fakeItems: [Item] = [ 47 | Item(id: Date().timestampInt, createdAt: Date(), details: "Run a project", isComplete: false), 48 | Item(id: Date().timestampInt, createdAt: Date(), details: "Add new RIB", isComplete: false), 49 | Item(id: Date().timestampInt, createdAt: Date(), details: "Wire new RIB", isComplete: false), 50 | Item(id: Date().timestampInt, createdAt: Date(), details: "Schedule a session", isComplete: true), 51 | ] 52 | 53 | func requestAccessToken(login: String, password: String, 54 | callback: @escaping ToDoServiceAuthCallback) { 55 | 56 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 57 | callback(.success(sessionToken: "owheoasdhfalsdif")) 58 | } 59 | } 60 | 61 | func getAllItems(callback: @escaping ToDoServiceItemsCallback) { 62 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 63 | callback(.success(items: self.fakeItems)) 64 | } 65 | } 66 | 67 | func getIncompleteItems(callback: @escaping ToDoServiceItemsCallback) { 68 | let incompleteItems = fakeItems.filter { !$0.isComplete } 69 | 70 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 71 | callback(.success(items: incompleteItems)) 72 | } 73 | } 74 | 75 | func getCompletedItems(callback: @escaping ToDoServiceItemsCallback) { 76 | let incompleteItems = fakeItems.filter { $0.isComplete } 77 | 78 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 79 | callback(.success(items: incompleteItems)) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ToDo/Utils/Date.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Date { 12 | public var timestamp: String { 13 | return String(format: "%.0f", timeIntervalSince1970 * 1000) 14 | } 15 | 16 | public var timestampInt: Int { 17 | return Int(round(timeIntervalSince1970 * 1000)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ToDo/Views/ItemCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemCell.swift 3 | // ToDo 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ItemCell: UITableViewCell { 12 | @IBOutlet private weak var detailsLabel: UILabel! 13 | @IBOutlet private weak var timeLabel: UILabel! 14 | @IBOutlet private weak var statusIndicator: UIView! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | // Initialization code 19 | } 20 | 21 | func display(item: ItemViewModel) { 22 | detailsLabel.text = item.details 23 | timeLabel.text = item.createdAt 24 | statusIndicator.backgroundColor = item.statusColor 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ToDo/Views/ItemCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 31 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /ToDoTests/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 | -------------------------------------------------------------------------------- /ToDoTests/ToDoTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToDoTests.swift 3 | // ToDoTests 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import ToDo 11 | 12 | class ToDoTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ToDoUITests/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 | -------------------------------------------------------------------------------- /ToDoUITests/ToDoUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToDoUITests.swift 3 | // ToDoUITests 4 | // 5 | // Created by Dmitry Klimkin on 1/2/18. 6 | // Copyright © 2018 Dev4Jam. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class ToDoUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------