├── .gitignore ├── .swiftlint.yml ├── .travis.yml ├── Cartfile ├── Cartfile.resolved ├── DeliTodo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── DeliTodo.xcscheme ├── DeliTodo.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── DeliTodo ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon@1024w.png │ │ │ ├── Icon@120w.png │ │ │ ├── Icon@152w.png │ │ │ ├── Icon@167w.png │ │ │ ├── Icon@180w.png │ │ │ ├── Icon@20w.png │ │ │ ├── Icon@256w.png │ │ │ ├── Icon@29w.png │ │ │ ├── Icon@40w.png │ │ │ ├── Icon@512w.png │ │ │ ├── Icon@58w.png │ │ │ ├── Icon@60w.png │ │ │ ├── Icon@76w.png │ │ │ ├── Icon@80w.png │ │ │ └── Icon@87w.png │ │ ├── Contents.json │ │ ├── tab-more-normal.imageset │ │ │ ├── Contents.json │ │ │ ├── tab-more-normal.png │ │ │ ├── tab-more-normal@2x.png │ │ │ └── tab-more-normal@3x.png │ │ ├── tab-more-selected.imageset │ │ │ ├── Contents.json │ │ │ ├── tab-more-selected.png │ │ │ ├── tab-more-selected@2x.png │ │ │ └── tab-more-selected@3x.png │ │ ├── tab-todo-normal.imageset │ │ │ ├── Contents.json │ │ │ ├── tab-todo-normal.png │ │ │ ├── tab-todo-normal@2x.png │ │ │ └── tab-todo-normal@3x.png │ │ └── tab-todo-selected.imageset │ │ │ ├── Contents.json │ │ │ ├── tab-todo-selected.png │ │ │ ├── tab-todo-selected@2x.png │ │ │ └── tab-todo-selected@3x.png │ ├── Launch Screen.storyboard │ └── Settings.bundle │ │ ├── Root.plist │ │ ├── com.mono0926.LicensePlist.latest_result.txt │ │ ├── com.mono0926.LicensePlist.plist │ │ ├── com.mono0926.LicensePlist │ │ ├── DeepLinkKit.plist │ │ ├── Deli.plist │ │ ├── Gradients.plist │ │ ├── MarqueeLabel.plist │ │ ├── Nimble.plist │ │ ├── NotificationBanner.plist │ │ ├── Quick.plist │ │ ├── ReactorKit-Carthage.plist │ │ ├── ReactorKit.plist │ │ ├── RxDataSources.plist │ │ ├── RxOptional.plist │ │ ├── RxSwift.plist │ │ ├── RxValidator.plist │ │ ├── SnapKit.plist │ │ ├── SwiftGen.plist │ │ ├── SwiftLint.plist │ │ ├── SwiftyBeaver.plist │ │ ├── SwipeCellKit.plist │ │ ├── TextFieldEffects.plist │ │ ├── Umbrella.plist │ │ └── realm-cocoa.plist │ │ └── en.lproj │ │ └── Root.strings ├── Sources │ ├── AppDelegate.swift │ ├── Assets.swift │ ├── Configurations │ │ ├── AnalyticsConfiguration.swift │ │ ├── FabricConfiguration.swift │ │ ├── FirebaseConfiguration.swift │ │ ├── NavigatorConfiguration.swift │ │ ├── RealmConfiguration.swift │ │ ├── RxSchedulerConfiguration.swift │ │ └── WindowConfiguration.swift │ ├── Cores │ │ ├── Analytics │ │ │ └── AnalyticsEvent.swift │ │ ├── Logging │ │ │ └── Logger.swift │ │ ├── Navigating │ │ │ ├── Impl │ │ │ │ └── NavigatorImpl.swift │ │ │ └── Navigator.swift │ │ └── UniqueManager.swift │ ├── DeliFactory.swift │ ├── Errors │ │ └── CommonError.swift │ ├── Extensions │ │ ├── Rx │ │ │ ├── FirebaseAuth+Rx.swift │ │ │ ├── FirebaseDatabase+Rx.swift │ │ │ ├── Realm+Rx.swift │ │ │ └── UIViewController+Rx.swift │ │ └── UIColor+Image.swift │ ├── Models │ │ ├── Credential.swift │ │ ├── Realm │ │ │ └── TodoTable.swift │ │ ├── Toast.swift │ │ ├── Todo.swift │ │ └── User.swift │ ├── Repositories │ │ ├── Impl │ │ │ ├── FirebaseTodoRepositoryImpl.swift │ │ │ └── RealmTodoRepositoryImpl.swift │ │ └── TodoRepository.swift │ ├── Sections │ │ ├── MoreSection.swift │ │ └── TodoSection.swift │ ├── Services │ │ ├── AuthService.swift │ │ ├── Impl │ │ │ ├── FirebaseAuthServiceImpl.swift │ │ │ ├── ToastServiceImpl.swift │ │ │ ├── TodoServiceImpl.swift │ │ │ └── TodoSyncServiceImpl.swift │ │ ├── SyncService.swift │ │ ├── ToastService.swift │ │ └── TodoService.swift │ ├── ViewControllers │ │ ├── AddTodoViewController.swift │ │ ├── AddTodoViewReactor.swift │ │ ├── LoginViewController.swift │ │ ├── LoginViewReactor.swift │ │ ├── MainNavigationViewController.swift │ │ ├── MainNavigationViewReactor.swift │ │ ├── MoreViewController.swift │ │ ├── MoreViewReactor.swift │ │ ├── SignUpViewController.swift │ │ ├── SignUpViewReactor.swift │ │ ├── SplashViewController.swift │ │ ├── SplashViewReactor.swift │ │ ├── ToastViewController.swift │ │ ├── ToastViewReactor.swift │ │ ├── TodoViewController.swift │ │ └── TodoViewReactor.swift │ └── Views │ │ ├── MoreCell.swift │ │ ├── MoreCellReactor.swift │ │ ├── ToastView.swift │ │ ├── TodoCell.swift │ │ └── TodoCellReactor.swift └── Supporting Files │ ├── GoogleService-Info.plist │ └── Info.plist ├── DeliTodoTests ├── Sources │ ├── Configuration │ │ └── DeliConfiguration.swift │ ├── Dummies │ │ ├── DummyTodo.swift │ │ └── DummyUser.swift │ ├── Mocks │ │ ├── Cores │ │ │ └── Analytics │ │ │ │ └── MockAnalyticsProvider.swift │ │ ├── Errors │ │ │ └── MockError.swift │ │ ├── Repositories │ │ │ └── MockTodoRepository.swift │ │ └── Services │ │ │ ├── MockAuthService.swift │ │ │ ├── MockToastService.swift │ │ │ └── MockTodoService.swift │ ├── Reactors │ │ ├── AddTodoViewReactorSpec.swift │ │ ├── LoginViewReactorSpec.swift │ │ ├── MainNavigationViewReactorSpec.swift │ │ ├── SignUpViewReactorSpec.swift │ │ └── SplashViewReactorSpec.swift │ ├── Services │ │ ├── ToastServiceSpec.swift │ │ ├── TodoServiceSpec.swift │ │ └── TodoSyncServiceSpec.swift │ └── TestMain.swift └── Supporting Files │ └── Info.plist ├── Design └── Design.sketch ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── codecov.yml ├── deli.html ├── deli.yml ├── license_plist.yml └── swiftgen.yml /.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 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 62 | 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | 68 | */*.xcodeproj/ 69 | .idea/ 70 | *.zip 71 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - DeliTodo 3 | 4 | excluded: 5 | - DeliTodo/Sources/DeliFactory.swift 6 | 7 | whitelist_rules: 8 | - closing_brace 9 | - closure_end_indentation 10 | - closure_spacing 11 | - contains_over_first_not_nil 12 | - colon 13 | - comma 14 | - control_statement 15 | - discarded_notification_center_observer 16 | - empty_enum_arguments 17 | - empty_parameters 18 | - empty_parentheses_with_trailing_closure 19 | - first_where 20 | - for_where 21 | - force_cast 22 | - identifier_name 23 | - leading_whitespace 24 | - legacy_cggeometry_functions 25 | - legacy_constant 26 | - legacy_constructor 27 | - legacy_nsgeometry_functions 28 | - multiline_parameters 29 | - opening_brace 30 | - operator_usage_whitespace 31 | - private_over_fileprivate 32 | - redundant_discardable_let 33 | - redundant_nil_coalescing 34 | - redundant_void_return 35 | - return_arrow_whitespace 36 | - sorted_first_last 37 | - syntactic_sugar 38 | - trailing_semicolon 39 | - vertical_whitespace 40 | - void_return 41 | - switch_case_on_newline 42 | - let_var_whitespace 43 | 44 | identifier_name: 45 | max_length: 46 | warning: 61 47 | excluded: 48 | - id 49 | - ID 50 | - db 51 | - i 52 | - j 53 | - ok 54 | - no 55 | - ss 56 | - on 57 | - qa 58 | - to 59 | - me 60 | - s3 61 | - qq 62 | - x 63 | - y 64 | - z 65 | - dx 66 | - dy 67 | - up 68 | - of 69 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode10 2 | language: objective-c 3 | sudo: required 4 | branches: 5 | only: 6 | - master 7 | - develop 8 | - /^v[\d.]+$/ 9 | except: 10 | - screenshot 11 | 12 | env: 13 | global: 14 | - WORKSPACE="DeliTodo.xcworkspace" 15 | - SCHEME="DeliTodo" 16 | matrix: 17 | - DESTINATION="platform=iOS Simulator,name=iPhone 8,OS=12.0" 18 | 19 | install: 20 | - gem install cocoapods 21 | 22 | before_script: 23 | - set -o pipefail 24 | - pod install --repo-update 25 | - carthage update --cache-builds --platform iOS 26 | 27 | script: 28 | - xcodebuild clean build test 29 | -workspace "$WORKSPACE" 30 | -scheme "$SCHEME" 31 | -destination "$DESTINATION" 32 | -configuration Debug 33 | -enableCodeCoverage YES 34 | CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO | xcpretty -c 35 | 36 | - bash <(curl -s https://codecov.io/bash) -X xcodeplist -J 'DeliTodo' 37 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | # Architecture 2 | binary "https://kawoou.kr/ReactorKit-Carthage/ReactorKit-Static" 3 | 4 | # Rx 5 | github "ReactiveX/RxSwift" "4.3.1" 6 | github "RxSwiftCommunity/RxOptional" "3.5.0" 7 | github "RxSwiftCommunity/RxDataSources" "3.1.0" 8 | github "kawoou/RxValidator" "94750bdcd89b8d2f84682c949f6893b0d44a37e1" 9 | 10 | # Database 11 | binary "https://github.com/kawoou/DeliTodo/raw/realm/Realm" 12 | #github "realm/realm-cocoa" 13 | 14 | # Logging 15 | github "SwiftyBeaver/SwiftyBeaver" "1.6.1" 16 | 17 | # Deeplink 18 | github "button/DeepLinkKit" "1.5.0" 19 | 20 | # UI 21 | github "SnapKit/SnapKit" "4.0.1" 22 | github "cruisediary/Gradients" "0.1.4" 23 | github "raulriera/TextFieldEffects" "1.5.0" 24 | github "SwipeCellKit/SwipeCellKit" "2.5.0" 25 | github "Daltron/NotificationBanner" "1.7.3" 26 | 27 | # Test 28 | github "Quick/Quick" "v1.3.2" 29 | github "Quick/Nimble" "v7.3.1" 30 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | binary "https://github.com/kawoou/DeliTodo/raw/realm/Realm" "3.11.0" 2 | binary "https://kawoou.kr/ReactorKit-Carthage/ReactorKit-Static" "1.2.0" 3 | github "Daltron/NotificationBanner" "1.7.3" 4 | github "Quick/Nimble" "v7.3.1" 5 | github "Quick/Quick" "v1.3.2" 6 | github "ReactiveX/RxSwift" "4.3.1" 7 | github "RxSwiftCommunity/RxDataSources" "3.1.0" 8 | github "RxSwiftCommunity/RxOptional" "3.5.0" 9 | github "SnapKit/SnapKit" "4.0.1" 10 | github "SwiftyBeaver/SwiftyBeaver" "1.6.1" 11 | github "SwipeCellKit/SwipeCellKit" "2.5.0" 12 | github "button/DeepLinkKit" "1.5.0" 13 | github "cbpowell/MarqueeLabel" "3.2.0" 14 | github "cruisediary/Gradients" "0.1.4" 15 | github "kawoou/RxValidator" "94750bdcd89b8d2f84682c949f6893b0d44a37e1" 16 | github "raulriera/TextFieldEffects" "1.5.0" 17 | -------------------------------------------------------------------------------- /DeliTodo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DeliTodo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DeliTodo.xcodeproj/xcshareddata/xcschemes/DeliTodo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 39 | 40 | 41 | 42 | 44 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /DeliTodo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DeliTodo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon@40w.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon@60w.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon@29w.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon@58w.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon@87w.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon@80w.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon@120w.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon@120w.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon@180w.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon@20w.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon@40w.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon@29w.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon@58w.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon@40w.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon@80w.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon@76w.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon@152w.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon@167w.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon@1024w.png", 115 | "scale" : "1x" 116 | }, 117 | { 118 | "size" : "256x256", 119 | "idiom" : "mac", 120 | "filename" : "Icon@256w.png", 121 | "scale" : "1x" 122 | }, 123 | { 124 | "size" : "512x512", 125 | "idiom" : "mac", 126 | "filename" : "Icon@512w.png", 127 | "scale" : "1x" 128 | } 129 | ], 130 | "info" : { 131 | "version" : 1, 132 | "author" : "xcode" 133 | } 134 | } -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@1024w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@1024w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@120w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@120w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@152w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@152w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@167w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@167w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@180w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@180w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@20w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@20w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@256w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@256w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@29w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@29w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@40w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@40w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@512w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@512w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@58w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@58w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@60w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@60w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@76w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@76w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@80w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@80w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@87w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/AppIcon.appiconset/Icon@87w.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-more-normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-more-normal.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-more-normal@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-more-normal@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-more-normal.imageset/tab-more-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-more-normal.imageset/tab-more-normal.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-more-normal.imageset/tab-more-normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-more-normal.imageset/tab-more-normal@2x.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-more-normal.imageset/tab-more-normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-more-normal.imageset/tab-more-normal@3x.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-more-selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-more-selected.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-more-selected@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-more-selected@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-more-selected.imageset/tab-more-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-more-selected.imageset/tab-more-selected.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-more-selected.imageset/tab-more-selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-more-selected.imageset/tab-more-selected@2x.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-more-selected.imageset/tab-more-selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-more-selected.imageset/tab-more-selected@3x.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-todo-normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-todo-normal.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-todo-normal@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-todo-normal@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-todo-normal.imageset/tab-todo-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-todo-normal.imageset/tab-todo-normal.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-todo-normal.imageset/tab-todo-normal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-todo-normal.imageset/tab-todo-normal@2x.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-todo-normal.imageset/tab-todo-normal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-todo-normal.imageset/tab-todo-normal@3x.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-todo-selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "tab-todo-selected.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "tab-todo-selected@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "tab-todo-selected@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-todo-selected.imageset/tab-todo-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-todo-selected.imageset/tab-todo-selected.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-todo-selected.imageset/tab-todo-selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-todo-selected.imageset/tab-todo-selected@2x.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Assets.xcassets/tab-todo-selected.imageset/tab-todo-selected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Assets.xcassets/tab-todo-selected.imageset/tab-todo-selected@3x.png -------------------------------------------------------------------------------- /DeliTodo/Resources/Launch Screen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSChildPaneSpecifier 12 | Title 13 | Licenses 14 | File 15 | com.mono0926.LicensePlist 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist.latest_result.txt: -------------------------------------------------------------------------------- 1 | name: DeepLinkKit, nameSpecified: , owner: button, version: 1.5.0 2 | 3 | name: Deli, nameSpecified: , owner: kawoou, version: 0.5.0 4 | 5 | name: Gradients, nameSpecified: , owner: cruisediary, version: 0.1.4 6 | 7 | name: MarqueeLabel, nameSpecified: , owner: cbpowell, version: 3.2.0 8 | 9 | name: Nimble, nameSpecified: , owner: Quick, version: v7.3.1 10 | 11 | name: NotificationBanner, nameSpecified: , owner: Daltron, version: 1.7.1 12 | 13 | name: Quick, nameSpecified: , owner: Quick, version: v1.3.2 14 | 15 | name: ReactorKit, nameSpecified: , owner: ReactorKit, version: 16 | 17 | name: ReactorKit-Carthage, nameSpecified: , owner: kawoou, version: 1.2.0 18 | 19 | name: realm-cocoa, nameSpecified: , owner: realm, version: v3.11.0 20 | 21 | name: RxDataSources, nameSpecified: , owner: RxSwiftCommunity, version: 3.1.0 22 | 23 | name: RxOptional, nameSpecified: , owner: RxSwiftCommunity, version: 3.5.0 24 | 25 | name: RxSwift, nameSpecified: , owner: ReactiveX, version: 4.3.1 26 | 27 | name: RxValidator, nameSpecified: , owner: kawoou, version: 94750bd 28 | 29 | name: SnapKit, nameSpecified: , owner: SnapKit, version: 4.0.1 30 | 31 | name: SwiftGen, nameSpecified: , owner: SwiftGen, version: 6.0.1 32 | 33 | name: SwiftLint, nameSpecified: , owner: Realm, version: 0.27.0 34 | 35 | name: SwiftyBeaver, nameSpecified: , owner: SwiftyBeaver, version: 1.6.1 36 | 37 | name: SwipeCellKit, nameSpecified: , owner: SwipeCellKit, version: 2.5.0 38 | 39 | name: TextFieldEffects, nameSpecified: , owner: raulriera, version: 1.5.0 40 | 41 | name: Umbrella, nameSpecified: , owner: devxoul, version: 0.7.1 42 | 43 | add-version-numbers: false 44 | 45 | LicensePlist Version: 1.8.3 -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | File 9 | com.mono0926.LicensePlist/DeepLinkKit 10 | Title 11 | DeepLinkKit 12 | Type 13 | PSChildPaneSpecifier 14 | 15 | 16 | File 17 | com.mono0926.LicensePlist/Deli 18 | Title 19 | Deli 20 | Type 21 | PSChildPaneSpecifier 22 | 23 | 24 | File 25 | com.mono0926.LicensePlist/Gradients 26 | Title 27 | Gradients 28 | Type 29 | PSChildPaneSpecifier 30 | 31 | 32 | File 33 | com.mono0926.LicensePlist/MarqueeLabel 34 | Title 35 | MarqueeLabel 36 | Type 37 | PSChildPaneSpecifier 38 | 39 | 40 | File 41 | com.mono0926.LicensePlist/Nimble 42 | Title 43 | Nimble 44 | Type 45 | PSChildPaneSpecifier 46 | 47 | 48 | File 49 | com.mono0926.LicensePlist/NotificationBanner 50 | Title 51 | NotificationBanner 52 | Type 53 | PSChildPaneSpecifier 54 | 55 | 56 | File 57 | com.mono0926.LicensePlist/Quick 58 | Title 59 | Quick 60 | Type 61 | PSChildPaneSpecifier 62 | 63 | 64 | File 65 | com.mono0926.LicensePlist/ReactorKit 66 | Title 67 | ReactorKit 68 | Type 69 | PSChildPaneSpecifier 70 | 71 | 72 | File 73 | com.mono0926.LicensePlist/ReactorKit-Carthage 74 | Title 75 | ReactorKit-Carthage 76 | Type 77 | PSChildPaneSpecifier 78 | 79 | 80 | File 81 | com.mono0926.LicensePlist/realm-cocoa 82 | Title 83 | realm-cocoa 84 | Type 85 | PSChildPaneSpecifier 86 | 87 | 88 | File 89 | com.mono0926.LicensePlist/RxDataSources 90 | Title 91 | RxDataSources 92 | Type 93 | PSChildPaneSpecifier 94 | 95 | 96 | File 97 | com.mono0926.LicensePlist/RxOptional 98 | Title 99 | RxOptional 100 | Type 101 | PSChildPaneSpecifier 102 | 103 | 104 | File 105 | com.mono0926.LicensePlist/RxSwift 106 | Title 107 | RxSwift 108 | Type 109 | PSChildPaneSpecifier 110 | 111 | 112 | File 113 | com.mono0926.LicensePlist/RxValidator 114 | Title 115 | RxValidator 116 | Type 117 | PSChildPaneSpecifier 118 | 119 | 120 | File 121 | com.mono0926.LicensePlist/SnapKit 122 | Title 123 | SnapKit 124 | Type 125 | PSChildPaneSpecifier 126 | 127 | 128 | File 129 | com.mono0926.LicensePlist/SwiftGen 130 | Title 131 | SwiftGen 132 | Type 133 | PSChildPaneSpecifier 134 | 135 | 136 | File 137 | com.mono0926.LicensePlist/SwiftLint 138 | Title 139 | SwiftLint 140 | Type 141 | PSChildPaneSpecifier 142 | 143 | 144 | File 145 | com.mono0926.LicensePlist/SwiftyBeaver 146 | Title 147 | SwiftyBeaver 148 | Type 149 | PSChildPaneSpecifier 150 | 151 | 152 | File 153 | com.mono0926.LicensePlist/SwipeCellKit 154 | Title 155 | SwipeCellKit 156 | Type 157 | PSChildPaneSpecifier 158 | 159 | 160 | File 161 | com.mono0926.LicensePlist/TextFieldEffects 162 | Title 163 | TextFieldEffects 164 | Type 165 | PSChildPaneSpecifier 166 | 167 | 168 | File 169 | com.mono0926.LicensePlist/Umbrella 170 | Title 171 | Umbrella 172 | Type 173 | PSChildPaneSpecifier 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/DeepLinkKit.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | Copyright (c) 2015-2016 Button, Inc. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | Type 29 | PSGroupSpecifier 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/Deli.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | MIT License 10 | 11 | Copyright (c) 2018 Jungwon An 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/Gradients.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | MIT License 10 | 11 | Copyright © 2018 Cruz 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/MarqueeLabel.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | Copyright (c) 2011-2017 Charles Powell 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 12 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 13 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 14 | to permit persons to whom the Software is furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 21 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | IN THE SOFTWARE. 23 | Type 24 | PSGroupSpecifier 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/NotificationBanner.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | Copyright (c) 2017-2018 Daltron <daltonhint4@gmail.com> 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | 29 | Type 30 | PSGroupSpecifier 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/ReactorKit-Carthage.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | MIT License 10 | 11 | Copyright (c) 2018 Jungwon An 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/ReactorKit.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2017 Suyeol Jeon (xoul.kr) 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/RxDataSources.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | MIT License 10 | 11 | Copyright (c) 2017 RxSwift Community 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/RxOptional.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | Copyright (c) 2016 Thane Gill <me@thanegill.com> 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | 29 | Type 30 | PSGroupSpecifier 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/RxSwift.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | **The MIT License** 10 | **Copyright © 2015 Krunoslav Zaher** 11 | **All rights reserved.** 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | Type 19 | PSGroupSpecifier 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/RxValidator.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | MIT License 10 | 11 | Copyright (c) 2018 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/SnapKit.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | 29 | Type 30 | PSGroupSpecifier 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/SwiftGen.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | MIT License 10 | 11 | Copyright (c) 2018 SwiftGen 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/SwiftLint.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2015 Realm Inc. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/SwiftyBeaver.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2015 Sebastian Kreutzberger 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/SwipeCellKit.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | MIT License 10 | Copyright (c) 2017 Jeremy Koch 11 | 12 | http://jerkoch.com 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/TextFieldEffects.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2015 Raul Riera 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | 32 | Type 33 | PSGroupSpecifier 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/com.mono0926.LicensePlist/Umbrella.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2017 Suyeol Jeon (xoul.kr) 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | Type 32 | PSGroupSpecifier 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /DeliTodo/Resources/Settings.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/DeliTodo/Resources/Settings.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /DeliTodo/Sources/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import UIKit 11 | import DeepLinkKit 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate, Inject { 15 | 16 | // MARK: - Property 17 | 18 | let appContext = AppContext.shared.load([ 19 | DeliFactory.self 20 | ]) 21 | 22 | var window: UIWindow? 23 | 24 | lazy var router: DPLDeepLinkRouter = Inject(DPLDeepLinkRouter.self) 25 | lazy var navigator: Navigator = Inject(Navigator.self) 26 | 27 | // MARK: - UIApplicationDelegate 28 | 29 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 30 | window = Inject(UIWindow.self) 31 | navigator.navigate(to: .splash) 32 | 33 | return true 34 | } 35 | 36 | func applicationWillResignActive(_ application: UIApplication) { 37 | // 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. 38 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 39 | } 40 | 41 | func applicationDidEnterBackground(_ application: UIApplication) { 42 | // 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. 43 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 44 | } 45 | 46 | func applicationWillEnterForeground(_ application: UIApplication) { 47 | // 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. 48 | } 49 | 50 | func applicationDidBecomeActive(_ application: UIApplication) { 51 | // 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. 52 | } 53 | 54 | func applicationWillTerminate(_ application: UIApplication) { 55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 56 | } 57 | 58 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { 59 | return router.handle(url, withCompletion: nil) 60 | } 61 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 62 | return router.handle(userActivity, withCompletion: nil) 63 | } 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Assets.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen, by O.Halligon — https://github.com/SwiftGen/SwiftGen 3 | 4 | #if os(OSX) 5 | import AppKit.NSImage 6 | internal typealias AssetColorTypeAlias = NSColor 7 | internal typealias AssetImageTypeAlias = NSImage 8 | #elseif os(iOS) || os(tvOS) || os(watchOS) 9 | import UIKit.UIImage 10 | internal typealias AssetColorTypeAlias = UIColor 11 | internal typealias AssetImageTypeAlias = UIImage 12 | #endif 13 | 14 | // swiftlint:disable superfluous_disable_command 15 | // swiftlint:disable file_length 16 | 17 | // MARK: - Asset Catalogs 18 | 19 | // swiftlint:disable identifier_name line_length nesting type_body_length type_name 20 | internal enum Asset { 21 | internal static let tabMoreNormal = ImageAsset(name: "tab-more-normal") 22 | internal static let tabMoreSelected = ImageAsset(name: "tab-more-selected") 23 | internal static let tabTodoNormal = ImageAsset(name: "tab-todo-normal") 24 | internal static let tabTodoSelected = ImageAsset(name: "tab-todo-selected") 25 | } 26 | // swiftlint:enable identifier_name line_length nesting type_body_length type_name 27 | 28 | // MARK: - Implementation Details 29 | 30 | internal struct ColorAsset { 31 | internal fileprivate(set) var name: String 32 | 33 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, OSX 10.13, *) 34 | internal var color: AssetColorTypeAlias { 35 | return AssetColorTypeAlias(asset: self) 36 | } 37 | } 38 | 39 | internal extension AssetColorTypeAlias { 40 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, OSX 10.13, *) 41 | convenience init!(asset: ColorAsset) { 42 | let bundle = Bundle(for: BundleToken.self) 43 | #if os(iOS) || os(tvOS) 44 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 45 | #elseif os(OSX) 46 | self.init(named: NSColor.Name(asset.name), bundle: bundle) 47 | #elseif os(watchOS) 48 | self.init(named: asset.name) 49 | #endif 50 | } 51 | } 52 | 53 | internal struct DataAsset { 54 | internal fileprivate(set) var name: String 55 | 56 | #if os(iOS) || os(tvOS) || os(OSX) 57 | @available(iOS 9.0, tvOS 9.0, OSX 10.11, *) 58 | internal var data: NSDataAsset { 59 | return NSDataAsset(asset: self) 60 | } 61 | #endif 62 | } 63 | 64 | #if os(iOS) || os(tvOS) || os(OSX) 65 | @available(iOS 9.0, tvOS 9.0, OSX 10.11, *) 66 | internal extension NSDataAsset { 67 | convenience init!(asset: DataAsset) { 68 | let bundle = Bundle(for: BundleToken.self) 69 | #if os(iOS) || os(tvOS) 70 | self.init(name: asset.name, bundle: bundle) 71 | #elseif os(OSX) 72 | self.init(name: NSDataAsset.Name(asset.name), bundle: bundle) 73 | #endif 74 | } 75 | } 76 | #endif 77 | 78 | internal struct ImageAsset { 79 | internal fileprivate(set) var name: String 80 | 81 | internal var image: AssetImageTypeAlias { 82 | let bundle = Bundle(for: BundleToken.self) 83 | #if os(iOS) || os(tvOS) 84 | let image = AssetImageTypeAlias(named: name, in: bundle, compatibleWith: nil) 85 | #elseif os(OSX) 86 | let image = bundle.image(forResource: NSImage.Name(name)) 87 | #elseif os(watchOS) 88 | let image = AssetImageTypeAlias(named: name) 89 | #endif 90 | guard let result = image else { fatalError("Unable to load image named \(name).") } 91 | return result 92 | } 93 | } 94 | 95 | internal extension AssetImageTypeAlias { 96 | @available(iOS 1.0, tvOS 1.0, watchOS 1.0, *) 97 | @available(OSX, deprecated, 98 | message: "This initializer is unsafe on macOS, please use the ImageAsset.image property") 99 | convenience init!(asset: ImageAsset) { 100 | #if os(iOS) || os(tvOS) 101 | let bundle = Bundle(for: BundleToken.self) 102 | self.init(named: asset.name, in: bundle, compatibleWith: nil) 103 | #elseif os(OSX) 104 | self.init(named: NSImage.Name(asset.name)) 105 | #elseif os(watchOS) 106 | self.init(named: asset.name) 107 | #endif 108 | } 109 | } 110 | 111 | private final class BundleToken {} 112 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Configurations/AnalyticsConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticsConfiguration.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import Umbrella 11 | 12 | typealias AppAnalytics = Analytics 13 | 14 | final class AnalyticsConfiguration: Configuration { 15 | 16 | let analytics = Config(AppAnalytics.self) { 17 | let analytics = AppAnalytics() 18 | analytics.register(provider: FirebaseProvider()) 19 | return analytics 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Configurations/FabricConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FabricConfiguration.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 06/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Crashlytics 10 | import Deli 11 | import Fabric 12 | 13 | final class FabricConfiguration: Configuration { 14 | 15 | let fabric = Config(Fabric.self, scope: .always) { 16 | return Fabric.with([Crashlytics.self]) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Configurations/FirebaseConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseConfiguration.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import FirebaseAuth 11 | import FirebaseCore 12 | import FirebaseDatabase 13 | 14 | final class FirebaseConfiguration: Configuration { 15 | 16 | let firebase = Config(FirebaseApp.self, scope: .always) { 17 | FirebaseCore.FirebaseConfiguration.shared.setLoggerLevel(.warning) 18 | 19 | if FirebaseApp.app() == nil { 20 | FirebaseApp.configure() 21 | } 22 | return FirebaseApp.app()! 23 | } 24 | 25 | let auth = Config(Auth.self, FirebaseApp.self) { app in 26 | return Auth.auth(app: app) 27 | } 28 | let database = Config(Database.self, FirebaseApp.self) { app in 29 | return Database.database(app: app) 30 | } 31 | 32 | let todoTable = Config(DatabaseReference.self, Database.self, qualifier: "todo", scope: .weak) { database in 33 | return database.reference().child("todo") 34 | } 35 | let userTable = Config(DatabaseReference.self, Database.self, qualifier: "user", scope: .weak) { database in 36 | return database.reference().child("user") 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Configurations/NavigatorConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigatorConfiguration.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import DeepLinkKit 11 | 12 | final class NavigatorConfiguration: Configuration { 13 | 14 | let router = Config(DPLDeepLinkRouter.self, Navigator.self) { navigator in 15 | let router = DPLDeepLinkRouter() 16 | 17 | router.register("/main") { _ in 18 | navigator.navigate(to: .main) 19 | } 20 | 21 | return router 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Configurations/RealmConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmConfiguration.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import Deli 12 | import RealmSwift 13 | 14 | final class RealmConfiguration: Configuration { 15 | 16 | // MARK: - Constant 17 | 18 | private struct Constant { 19 | static let schemeVersion: UInt64 = 1 20 | } 21 | 22 | let database = Config(Realm.self, scope: .always) { 23 | let realmPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] 24 | 25 | // try? FileManager.default.removeItem( 26 | // at: URL(fileURLWithPath: realmPath).appendingPathComponent("DeliTodo.realm") 27 | // ) 28 | 29 | let configuration = Realm.Configuration( 30 | fileURL: URL(fileURLWithPath: realmPath).appendingPathComponent("DeliTodo.realm"), 31 | readOnly: false, 32 | schemaVersion: Constant.schemeVersion, 33 | migrationBlock: { (migration, oldSchemeVersion) in 34 | // TODO: Writing after... 35 | } 36 | ) 37 | Realm.Configuration.defaultConfiguration = configuration 38 | 39 | return try! Realm() 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Configurations/RxSchedulerConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxSchedulerConfiguration.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 06/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import RxSwift 11 | 12 | final class RxSchedulerConfiguration: Configuration { 13 | 14 | let realmScheduler = Config(SchedulerType.self, qualifier: "realm") { 15 | return MainScheduler.asyncInstance 16 | } 17 | let syncScheduler = Config(SchedulerType.self, qualifier: "sync") { 18 | return SerialDispatchQueueScheduler(internalSerialQueueName: "io.kawoou.DeliTodo.sync") 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Configurations/WindowConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindowConfiguration.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import Deli 12 | 13 | final class WindowConfiguration: Configuration { 14 | 15 | let mainWindow = Config(UIWindow.self) { 16 | let window = UIWindow(frame: UIScreen.main.bounds) 17 | window.backgroundColor = .white 18 | return window 19 | } 20 | 21 | let toastWindow = Config(UIWindow.self, ToastViewController.self, qualifier: "toast", scope: .always) { toastViewController in 22 | let window = UIWindow(frame: UIScreen.main.bounds) 23 | window.rootViewController = toastViewController 24 | window.backgroundColor = .clear 25 | window.windowLevel = .alert 26 | window.isUserInteractionEnabled = false 27 | window.isHidden = false 28 | return window 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Cores/Analytics/AnalyticsEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyticsEvent.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Umbrella 10 | 11 | enum AnalyticsEvent { 12 | /// Login Screen 13 | case loginDo 14 | case login(isSuccess: Bool) 15 | case loginUnknownError(message: String) 16 | 17 | /// Sign Up Screen 18 | case signUpDo 19 | case signUp(isSuccess: Bool) 20 | case signUpUnknownError(message: String) 21 | 22 | /// Main Screen 23 | case mainTabChange(index: Int) 24 | 25 | /// Todo Screen 26 | case todoAddTapped 27 | 28 | /// More Screen 29 | case moreLogout 30 | } 31 | 32 | extension AnalyticsEvent: EventType { 33 | func name(for provider: ProviderType) -> String? { 34 | switch self { 35 | case .loginDo: 36 | return "login_do" 37 | case .login: 38 | return "login" 39 | case .loginUnknownError: 40 | return "login_unknown_error" 41 | case .signUpDo: 42 | return "signup_do" 43 | case .signUp: 44 | return "signup" 45 | case .signUpUnknownError: 46 | return "signup_unknown_error" 47 | case .mainTabChange: 48 | return "main_tab_change" 49 | case .todoAddTapped: 50 | return "todo_add_tap" 51 | case .moreLogout: 52 | return "more_logout" 53 | } 54 | } 55 | func parameters(for provider: ProviderType) -> [String: Any]? { 56 | switch self { 57 | case .loginDo: 58 | return nil 59 | 60 | case let .login(isSuccess): 61 | return ["success": isSuccess] 62 | 63 | case let .loginUnknownError(message): 64 | return ["message": message] 65 | 66 | case .signUpDo: 67 | return nil 68 | 69 | case let .signUp(isSuccess): 70 | return ["success": isSuccess] 71 | 72 | case let .signUpUnknownError(message): 73 | return ["message": message] 74 | 75 | case let .mainTabChange(index): 76 | return ["tab_index": index] 77 | 78 | case .todoAddTapped: 79 | return nil 80 | 81 | case .moreLogout: 82 | return nil 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Cores/Logging/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import SwiftyBeaver 10 | 11 | let logger: SwiftyBeaver.Type = { 12 | let console = ConsoleDestination() 13 | let file = FileDestination() 14 | let cloud = SBPlatformDestination(appID: "2kYemZ", appSecret: "cjw6k6mgrnFkcqjsmncRkq7hwsAgNwor", encryptionKey: "jCzf8zpflXbljxpvwKuvs9utyhpuAgxc") 15 | 16 | console.format = "$DHH:mm:ss$d $L $M" 17 | 18 | $0.addDestination(console) 19 | $0.addDestination(file) 20 | $0.addDestination(cloud) 21 | return $0 22 | }(SwiftyBeaver.self) 23 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Cores/Navigating/Impl/NavigatorImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigatorImpl.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | 11 | final class NavigatorImpl: Navigator, Autowired { 12 | 13 | let window: UIWindow 14 | 15 | func navigate(to target: NavigatorTarget) { 16 | switch target { 17 | case .splash: 18 | window.rootViewController = Inject(SplashViewController.self) 19 | case .login: 20 | window.rootViewController = Inject(LoginViewController.self) 21 | case .main: 22 | window.rootViewController = Inject(MainNavigationViewController.self) 23 | } 24 | } 25 | 26 | required init(_ window: UIWindow) { 27 | self.window = window 28 | 29 | window.makeKeyAndVisible() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Cores/Navigating/Navigator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Navigator.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | enum NavigatorTarget { 10 | case splash 11 | case login 12 | case main 13 | } 14 | 15 | protocol Navigator { 16 | func navigate(to target: NavigatorTarget) 17 | } 18 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Cores/UniqueManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UniqueManager.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 08/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class UniqueManager: NSObject { 12 | 13 | // MARK: - Private 14 | 15 | /// 64(0xFF) Characters 16 | static private let generateCharacters = [ 17 | "0", "1", "2", "3", "4", "5", "6", "7", 18 | "8", "9", "a", "b", "c", "d", "e", "f", 19 | "g", "h", "i", "j", "k", "l", "n", "m", 20 | "o", "p", "q", "r", "s", "t", "u", "v", 21 | "w", "x", "y", "z", "A", "B", "C", "D", 22 | "E", "F", "G", "H", "I", "J", "K", "L", 23 | "N", "M", "O", "P", "Q", "R", "S", "T", 24 | "U", "V", "W", "X", "Y", "Z", "-", "_" 25 | ] 26 | static private var equalIndex: Int = 0 27 | static private var oldKey1: Int = 0 28 | static private var oldKey2: Int = 0 29 | 30 | // MARK: - Public 31 | 32 | static public func generate() -> String { 33 | var id = "" 34 | func doGenerate() { 35 | let timestamp = NSDate().timeIntervalSince1970 36 | let timestampInt = Int(timestamp) 37 | 38 | let keycodeSize = Int(log2(Double(generateCharacters.count))) 39 | let key1 = timestampInt - 1441270000 40 | let key2 = Int((timestamp - Double(timestampInt)) * 7) 41 | 42 | if oldKey1 != key1 && oldKey2 != key2 { 43 | equalIndex = 0 44 | } 45 | 46 | let nKey1 = String(key1, radix: 2) 47 | let nKey2Raw = String(key2, radix: 2) 48 | let nKey2 = String(repeating: "0", count: 3 - nKey2Raw.count) + nKey2Raw 49 | let nKey3Raw = String(equalIndex, radix: 2) 50 | let nKey3 = String(repeating: "0", count: 10 - nKey3Raw.count) + nKey3Raw 51 | 52 | var key = nKey1 + nKey2 + nKey3 53 | var keyLen = key.lengthOfBytes(using: .utf8) 54 | 55 | key = key.padding( 56 | toLength: keyLen + keycodeSize - (keyLen % keycodeSize), 57 | withPad: "0", 58 | startingAt: 0 59 | ) 60 | keyLen = key.lengthOfBytes(using: .utf8) 61 | 62 | let linkLen = Int(ceil(Double(keyLen) / Double(keycodeSize))) 63 | 64 | oldKey1 = key1 65 | oldKey2 = key2 66 | equalIndex += 1 67 | 68 | for i in 0...(linkLen - 1) { 69 | let start = key.index(key.startIndex, offsetBy: i * 6) 70 | let end = key.index(key.startIndex, offsetBy: (i + 1) * 6 - 1) 71 | id += generateCharacters[Int(key[start...end], radix: 2)!] 72 | } 73 | } 74 | 75 | if Thread.isMainThread { 76 | doGenerate() 77 | } else { 78 | DispatchQueue.main.sync { 79 | doGenerate() 80 | } 81 | } 82 | 83 | return id 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Errors/CommonError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonError.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 06/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum CommonError: Error { 12 | case nilSelf 13 | } 14 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Extensions/Rx/FirebaseAuth+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseAuth+Rx.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import FirebaseAuth 10 | import RxSwift 11 | 12 | enum FirebaseAuthError: Error { 13 | case userNotFound 14 | case failedToCreateUser 15 | } 16 | 17 | extension Reactive where Base: UserProfileChangeRequest { 18 | func commitChanges() -> Single { 19 | return .create { [base] observer in 20 | base.commitChanges { error in 21 | if let error = error { 22 | observer(.error(error)) 23 | } else { 24 | observer(.success(Void())) 25 | } 26 | } 27 | return Disposables.create() 28 | } 29 | } 30 | } 31 | 32 | extension Reactive where Base: Auth { 33 | func createUser(withEmail email: String, password: String) -> Single { 34 | return .create { [base] observer in 35 | base.createUser(withEmail: email, password: password) { (result, error) in 36 | guard let result = result else { 37 | observer(.error(FirebaseAuthError.failedToCreateUser)) 38 | return 39 | } 40 | if let error = error { 41 | observer(.error(error)) 42 | } else { 43 | observer(.success(result)) 44 | } 45 | } 46 | return Disposables.create() 47 | } 48 | } 49 | func signIn(withEmail email: String, password: String) -> Single { 50 | return .create { [base] observer in 51 | base.signIn(withEmail: email, password: password) { (result, error) in 52 | guard let result = result else { 53 | observer(.error(FirebaseAuthError.userNotFound)) 54 | return 55 | } 56 | if let error = error { 57 | observer(.error(error)) 58 | } else { 59 | observer(.success(result)) 60 | } 61 | } 62 | 63 | return Disposables.create() 64 | } 65 | } 66 | func signOut() -> Single { 67 | return .create { [base] observer in 68 | do { 69 | try base.signOut() 70 | observer(.success(Void())) 71 | } catch let error { 72 | observer(.error(error)) 73 | } 74 | return Disposables.create() 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Extensions/Rx/FirebaseDatabase+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseDatabase+Rx.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import FirebaseDatabase 10 | import RxSwift 11 | 12 | extension PrimitiveSequence where Trait == SingleTrait, Element == DataSnapshot { 13 | func checkExist(_ isExist: Bool = true, throwError: Error) -> Single { 14 | return flatMap { snapshot in 15 | guard snapshot.exists() == isExist else { throw throwError } 16 | return .just(snapshot) 17 | } 18 | } 19 | func checkChildren(_ hasChildren: Bool = true, throwError: Error) -> Single { 20 | return flatMap { snapshot in 21 | guard snapshot.hasChildren() == hasChildren else { throw throwError } 22 | return .just(snapshot) 23 | } 24 | } 25 | } 26 | 27 | extension Reactive where Base: DatabaseReference { 28 | func observe(of type: DataEventType) -> Observable { 29 | return .create { [base] observer in 30 | let token = base.observe(type, with: { 31 | observer.onNext($0) 32 | }, withCancel: { 33 | observer.onError($0) 34 | }) 35 | 36 | return Disposables.create { 37 | base.removeObserver(withHandle: token) 38 | } 39 | } 40 | } 41 | func observeSingleEvent(of type: DataEventType) -> Single { 42 | return .create { [base] observer in 43 | base.observeSingleEvent(of: type, with: { 44 | observer(.success($0)) 45 | }, withCancel: { 46 | observer(.error($0)) 47 | }) 48 | 49 | return Disposables.create() 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Extensions/Rx/Realm+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Realm+Rx.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RealmSwift 10 | import RxSwift 11 | 12 | extension Realm: ReactiveCompatible {} 13 | extension Reactive where Base: Realm { 14 | func object(of type: Element.Type, forPrimaryKey primaryKey: KeyType) -> Single where Element: Object { 15 | return .create { [base] observer in 16 | let object = base.object(ofType: type, forPrimaryKey: primaryKey) 17 | observer(.success(object)) 18 | 19 | return Disposables.create() 20 | } 21 | } 22 | func objects(of type: Element.Type) -> Single> where Element: Object { 23 | return .create { [base] observer in 24 | let objects = base.objects(type) 25 | observer(.success(objects)) 26 | 27 | return Disposables.create() 28 | } 29 | } 30 | 31 | func observe(of type: Element.Type, forPrimaryKey primaryKey: KeyType) -> Observable where Element: Object { 32 | return .create { [base] observer in 33 | let object = base.object(ofType: type, forPrimaryKey: primaryKey) 34 | observer.onNext(object) 35 | 36 | let token = object? 37 | .observe { result in 38 | switch result { 39 | case .change(let changes): 40 | changes.forEach { change in 41 | guard let value = change.newValue as? Element else { 42 | observer.onNext(nil) 43 | observer.onCompleted() 44 | return 45 | } 46 | observer.onNext(value) 47 | } 48 | 49 | case .deleted: 50 | observer.onNext(nil) 51 | observer.onCompleted() 52 | 53 | case .error(let error): 54 | observer.onError(error) 55 | } 56 | } 57 | 58 | return Disposables.create { 59 | token?.invalidate() 60 | } 61 | } 62 | } 63 | func observes(of type: Element.Type) -> Observable<[Element]> where Element: Object { 64 | return .create { [base] observer in 65 | let token = base.objects(type) 66 | .observe { result in 67 | switch result { 68 | case let .initial(results): 69 | observer.onNext(Array(results)) 70 | 71 | case let .update(results, _, _, _): 72 | observer.onNext(Array(results)) 73 | 74 | case let .error(error): 75 | observer.onError(error) 76 | } 77 | } 78 | 79 | return Disposables.create { 80 | token.invalidate() 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Extensions/Rx/UIViewController+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Rx.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 04/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxCocoa 11 | import RxSwift 12 | 13 | extension Reactive where Base: UIViewController { 14 | var viewDidLoad: ControlEvent { 15 | return ControlEvent( 16 | events: methodInvoked(#selector(Base.viewDidLoad)) 17 | .map { _ in Void() } 18 | ) 19 | } 20 | var viewWillAppear: ControlEvent { 21 | return ControlEvent( 22 | events: methodInvoked(#selector(Base.viewWillAppear)) 23 | .map { $0.first as? Bool ?? false } 24 | ) 25 | } 26 | var viewDidAppear: ControlEvent { 27 | return ControlEvent( 28 | events: methodInvoked(#selector(Base.viewDidAppear)) 29 | .map { $0.first as? Bool ?? false } 30 | ) 31 | } 32 | var viewWillDisappear: ControlEvent { 33 | return ControlEvent( 34 | events: methodInvoked(#selector(Base.viewWillDisappear)) 35 | .map { $0.first as? Bool ?? false } 36 | ) 37 | } 38 | var viewDidDisappear: ControlEvent { 39 | return ControlEvent( 40 | events: methodInvoked(#selector(Base.viewDidDisappear)) 41 | .map { $0.first as? Bool ?? false } 42 | ) 43 | } 44 | var viewWillLayoutSubviews: ControlEvent { 45 | return ControlEvent( 46 | events: methodInvoked(#selector(Base.viewWillLayoutSubviews)) 47 | .map { _ in Void() } 48 | ) 49 | } 50 | var viewDidLayoutSubviews: ControlEvent { 51 | return ControlEvent( 52 | events: methodInvoked(#selector(Base.viewDidLayoutSubviews)) 53 | .map { _ in Void() } 54 | ) 55 | } 56 | var didReceiveMemoryWarning: ControlEvent { 57 | return ControlEvent( 58 | events: methodInvoked(#selector(Base.didReceiveMemoryWarning)) 59 | .map { _ in Void() } 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Extensions/UIColor+Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Image.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 04/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | func image(_ size: CGSize = CGSize(width: 1, height: 1)) -> UIImage? { 13 | UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) 14 | defer { UIGraphicsEndImageContext() } 15 | 16 | setFill() 17 | UIRectFill(CGRect(origin: .zero, size: size)) 18 | 19 | guard let cgImage = UIGraphicsGetImageFromCurrentImageContext()?.cgImage else { return nil } 20 | return UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Models/Credential.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Credential.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | struct Credential { 10 | let email: String 11 | let password: String 12 | } 13 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Models/Realm/TodoTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmTodo.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import RealmSwift 12 | 13 | class TodoTable: Object { 14 | 15 | // MARK: - Property 16 | 17 | @objc dynamic var id: String = "" 18 | @objc dynamic var title = "" 19 | @objc dynamic var isChecked = false 20 | @objc dynamic var createdAt = Date() 21 | @objc dynamic var updatedAt = Date() 22 | 23 | // MARK: - Realm 24 | 25 | override class func primaryKey() -> String? { 26 | return "id" 27 | } 28 | override class func indexedProperties() -> [String] { 29 | return ["updatedAt"] 30 | } 31 | } 32 | extension Todo { 33 | init(_ table: TodoTable) { 34 | self.id = table.id 35 | self.title = table.title 36 | self.isChecked = table.isChecked 37 | self.createdAt = table.createdAt 38 | self.updatedAt = table.updatedAt 39 | } 40 | } 41 | extension TodoTable { 42 | convenience init(_ todo: Todo) { 43 | self.init() 44 | self.id = todo.id 45 | self.title = todo.title 46 | self.isChecked = todo.isChecked 47 | self.createdAt = todo.createdAt 48 | self.updatedAt = todo.updatedAt 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Models/Toast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Toast.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Toast: Equatable { 12 | enum Level { 13 | case normal 14 | case debug 15 | } 16 | 17 | let uuid: UUID = UUID() 18 | let message: String 19 | let level: Level 20 | let duration: Int 21 | } 22 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Models/Todo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Todo.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Todo { 12 | var id: String 13 | var title: String 14 | var isChecked: Bool 15 | var createdAt: Date 16 | var updatedAt: Date 17 | } 18 | extension Todo: Equatable { 19 | static func ==(lhs: Todo, rhs: Todo) -> Bool { 20 | guard lhs.id == rhs.id else { return false } 21 | guard lhs.title == rhs.title else { return false } 22 | guard lhs.isChecked == rhs.isChecked else { return false } 23 | guard Int64(lhs.createdAt.timeIntervalSince1970) == Int64(rhs.createdAt.timeIntervalSince1970) else { return false } 24 | guard Int64(lhs.updatedAt.timeIntervalSince1970) == Int64(rhs.updatedAt.timeIntervalSince1970) else { return false } 25 | return true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct User: Equatable { 12 | let id: String 13 | let email: String 14 | let name: String 15 | } 16 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Repositories/Impl/FirebaseTodoRepositoryImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseTodoRepositoryImpl.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import FirebaseDatabase 11 | import RxSwift 12 | 13 | final class FirebaseTodoRepositoryImpl: TodoRepository, Autowired { 14 | 15 | // MARK: - Deli 16 | 17 | var qualifier: String? = "remote" 18 | 19 | // MARK: - Private 20 | 21 | private let table: DatabaseReference 22 | 23 | private func convertTodo(id: String?) -> (DataSnapshot) throws -> Todo { 24 | return { snapshot in 25 | let id = id ?? snapshot.key 26 | 27 | guard let object = snapshot.value as? [String: Any] else { 28 | throw TodoRepositoryError.notFound 29 | } 30 | guard let title = object["title"] as? String else { 31 | throw TodoRepositoryError.notFound 32 | } 33 | guard let isChecked = object["isChecked"] as? NSNumber else { 34 | throw TodoRepositoryError.notFound 35 | } 36 | guard let createdAt = object["createdAt"] as? NSNumber else { 37 | throw TodoRepositoryError.notFound 38 | } 39 | guard let updatedAt = object["updatedAt"] as? NSNumber else { 40 | throw TodoRepositoryError.notFound 41 | } 42 | 43 | return Todo( 44 | id: id, 45 | title: title, 46 | isChecked: isChecked.boolValue, 47 | createdAt: Date(timeIntervalSince1970: createdAt.doubleValue), 48 | updatedAt: Date(timeIntervalSince1970: updatedAt.doubleValue) 49 | ) 50 | } 51 | } 52 | 53 | // MARK: - Public 54 | 55 | func get(id: String, for user: User) -> Single { 56 | return table.child("\(user.id)/\(id)").rx 57 | .observeSingleEvent(of: .value) 58 | .checkExist(throwError: TodoRepositoryError.notFound) 59 | .map(convertTodo(id: id)) 60 | } 61 | func gets(for user: User) -> Single<[Todo]> { 62 | return table.child("\(user.id)").rx 63 | .observeSingleEvent(of: .value) 64 | .map { [weak self] snapshot in 65 | guard let ss = self else { return [] } 66 | guard snapshot.hasChildren() else { return [] } 67 | guard let list = snapshot.children.allObjects as? [DataSnapshot] else { return [] } 68 | return try list.map(ss.convertTodo(id: nil)) 69 | } 70 | } 71 | 72 | func observe(id: String, for user: User) -> Observable { 73 | return table.child("\(user.id)/\(id)").rx 74 | .observe(of: .value) 75 | .map(convertTodo(id: id)) 76 | } 77 | func observes(for user: User) -> Observable<[Todo]> { 78 | return table.child("\(user.id)").rx 79 | .observe(of: .value) 80 | .map { [weak self] snapshot in 81 | guard let ss = self else { return [] } 82 | guard let list = snapshot.children.allObjects as? [DataSnapshot] else { return [] } 83 | return try list.map(ss.convertTodo(id: nil)) 84 | } 85 | } 86 | 87 | func insert(_ todo: Todo, for user: User) -> Single { 88 | return .create { [weak self] observer in 89 | guard let ss = self else { 90 | observer(.error(CommonError.nilSelf)) 91 | return Disposables.create() 92 | } 93 | let key = todo.id 94 | 95 | let dict = NSMutableDictionary() 96 | dict.setValue(todo.title, forKey: "title") 97 | dict.setValue(NSNumber(value: todo.isChecked), forKey: "isChecked") 98 | dict.setValue(NSNumber(value: todo.createdAt.timeIntervalSince1970), forKey: "createdAt") 99 | dict.setValue(NSNumber(value: todo.updatedAt.timeIntervalSince1970), forKey: "updatedAt") 100 | 101 | ss.table.child("\(user.id)/\(key)") 102 | .setValue(dict) 103 | 104 | observer(.success(key)) 105 | return Disposables.create() 106 | } 107 | } 108 | func update(_ todo: Todo, for user: User) -> Single { 109 | return .create { [weak self] observer in 110 | guard let ss = self else { 111 | observer(.error(CommonError.nilSelf)) 112 | return Disposables.create() 113 | } 114 | let dict = NSMutableDictionary() 115 | dict.setValue(todo.title, forKey: "title") 116 | dict.setValue(NSNumber(value: todo.isChecked), forKey: "isChecked") 117 | dict.setValue(NSNumber(value: todo.createdAt.timeIntervalSince1970), forKey: "createdAt") 118 | dict.setValue(NSNumber(value: todo.updatedAt.timeIntervalSince1970), forKey: "updatedAt") 119 | 120 | ss.table.child("\(user.id)/\(todo.id)") 121 | .setValue(dict) 122 | 123 | observer(.success(Void())) 124 | return Disposables.create() 125 | } 126 | } 127 | func delete(id: String, for user: User) -> Single { 128 | return .create { [weak self] observer in 129 | self?.table.child("\(user.id)/\(id)").removeValue() 130 | 131 | observer(.success(Void())) 132 | return Disposables.create() 133 | } 134 | } 135 | func clear(for user: User) -> Single { 136 | return .create { [weak self] observer in 137 | self?.table.child("\(user.id)").removeValue() 138 | 139 | observer(.success(Void())) 140 | return Disposables.create() 141 | } 142 | } 143 | 144 | // MARK: - Lifecycle 145 | 146 | required init(todo table: DatabaseReference) { 147 | self.table = table 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Repositories/Impl/RealmTodoRepositoryImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmTodoRepositoryImpl.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import RealmSwift 11 | import RxSwift 12 | 13 | final class RealmTodoRepositoryImpl: TodoRepository, Autowired { 14 | 15 | // MARK: - Deli 16 | 17 | var qualifier: String? = "local" 18 | var scheduler: SchedulerType 19 | 20 | // MARK: - Private 21 | 22 | private let realm: Realm 23 | 24 | // MARK: - Public 25 | 26 | func get(id: String, for user: User) -> Single { 27 | return realm.rx.object(of: TodoTable.self, forPrimaryKey: id) 28 | .map { todo in 29 | guard let todo = todo else { throw TodoRepositoryError.notFound } 30 | return Todo(todo) 31 | } 32 | } 33 | func gets(for user: User) -> Single<[Todo]> { 34 | return realm.rx.objects(of: TodoTable.self) 35 | .map { $0.map { Todo($0) } } 36 | } 37 | 38 | func observe(id: String, for user: User) -> Observable { 39 | return realm.rx.observe(of: TodoTable.self, forPrimaryKey: id) 40 | .map { todo in 41 | guard let todo = todo else { return nil } 42 | return Todo(todo) 43 | } 44 | } 45 | func observes(for user: User) -> Observable<[Todo]> { 46 | return realm.rx.observes(of: TodoTable.self) 47 | .map { $0.map { Todo($0) } } 48 | } 49 | 50 | func insert(_ todo: Todo, for user: User) -> Single { 51 | return Single 52 | .create { [weak self] observer in 53 | guard let ss = self else { 54 | observer(.error(CommonError.nilSelf)) 55 | return Disposables.create() 56 | } 57 | do { 58 | ss.realm.beginWrite() 59 | ss.realm.add(TodoTable(todo)) 60 | try ss.realm.commitWrite() 61 | 62 | observer(.success(todo.title)) 63 | } catch let error { 64 | observer(.error(error)) 65 | } 66 | return Disposables.create() 67 | } 68 | .subscribeOn(scheduler) 69 | } 70 | func update(_ todo: Todo, for user: User) -> Single { 71 | return Single 72 | .create { [weak self] observer in 73 | guard let ss = self else { 74 | observer(.error(CommonError.nilSelf)) 75 | return Disposables.create() 76 | } 77 | do { 78 | ss.realm.beginWrite() 79 | ss.realm.add(TodoTable(todo), update: true) 80 | try ss.realm.commitWrite() 81 | 82 | observer(.success(Void())) 83 | } catch let error { 84 | observer(.error(error)) 85 | } 86 | return Disposables.create() 87 | } 88 | .subscribeOn(scheduler) 89 | } 90 | func delete(id: String, for user: User) -> Single { 91 | return realm.rx.object(of: TodoTable.self, forPrimaryKey: id) 92 | .flatMap { [weak self] todo -> Single in 93 | guard let ss = self else { throw CommonError.nilSelf } 94 | guard let todo = todo else { throw TodoRepositoryError.notFound } 95 | 96 | do { 97 | ss.realm.beginWrite() 98 | ss.realm.delete(todo) 99 | try ss.realm.commitWrite() 100 | 101 | return .just(Void()) 102 | } catch let error { 103 | throw error 104 | } 105 | } 106 | .subscribeOn(scheduler) 107 | } 108 | func clear(for user: User) -> Single { 109 | return realm.rx.objects(of: TodoTable.self) 110 | .flatMap { [weak self] list -> Single in 111 | guard let ss = self else { throw CommonError.nilSelf } 112 | 113 | do { 114 | ss.realm.beginWrite() 115 | ss.realm.delete(list) 116 | try ss.realm.commitWrite() 117 | 118 | return .just(Void()) 119 | } catch let error { 120 | throw error 121 | } 122 | } 123 | .subscribeOn(scheduler) 124 | } 125 | 126 | // MARK: - Lifecycle 127 | 128 | required init(_ realm: Realm, realm scheduler: SchedulerType) { 129 | self.realm = realm 130 | self.scheduler = scheduler 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Repositories/TodoRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoRepository.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import RxSwift 12 | 13 | enum TodoRepositoryError: Error { 14 | case notFound 15 | case failedToInsert 16 | } 17 | 18 | protocol TodoRepository { 19 | func get(id: String, for user: User) -> Single 20 | func gets(for user: User) -> Single<[Todo]> 21 | 22 | func observe(id: String, for user: User) -> Observable 23 | func observes(for user: User) -> Observable<[Todo]> 24 | 25 | func insert(_ todo: Todo, for user: User) -> Single 26 | func update(_ todo: Todo, for user: User) -> Single 27 | func delete(id: String, for user: User) -> Single 28 | func clear(for user: User) -> Single 29 | } 30 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Sections/MoreSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoreSection.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 07/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxDataSources 10 | 11 | enum MoreSection { 12 | case items([MoreItem], title: String) 13 | } 14 | extension MoreSection: SectionModelType { 15 | var items: [MoreItem] { 16 | switch self { 17 | case let .items(items, _): 18 | return items 19 | } 20 | } 21 | 22 | init(original: MoreSection, items: [MoreItem]) { 23 | switch original { 24 | case .items: 25 | self = .items(items, title: "") 26 | } 27 | } 28 | } 29 | 30 | enum MoreItem { 31 | case row(MoreCellReactor) 32 | } 33 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Sections/TodoSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoSection.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 05/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxDataSources 10 | 11 | enum TodoSection { 12 | case items([TodoItem]) 13 | } 14 | extension TodoSection: SectionModelType { 15 | var items: [TodoItem] { 16 | switch self { 17 | case let .items(items): 18 | return items 19 | } 20 | } 21 | 22 | init(original: TodoSection, items: [TodoItem]) { 23 | switch original { 24 | case .items: 25 | self = .items(items) 26 | } 27 | } 28 | } 29 | 30 | enum TodoItem { 31 | case row(TodoCellReactor) 32 | } 33 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Services/AuthService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthService.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | protocol AuthService { 12 | func observeUser() -> Observable 13 | func currentUser() -> User? 14 | 15 | func signUp(credential: Credential, user: User) -> Single 16 | func login(credential: Credential) -> Single 17 | func logout() -> Single 18 | } 19 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Services/Impl/FirebaseAuthServiceImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirebaseAuthServiceImpl.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import FirebaseAuth 11 | import RxCocoa 12 | import RxSwift 13 | 14 | final class FirebaseAuthServiceImpl: AuthService, Autowired { 15 | 16 | // MARL: - Private 17 | 18 | private let auth: Auth 19 | 20 | private let userRelay = BehaviorRelay(value: nil) 21 | 22 | // MARK: - Public 23 | 24 | func observeUser() -> Observable { 25 | return userRelay.asObservable() 26 | } 27 | func currentUser() -> User? { 28 | return userRelay.value 29 | } 30 | func signUp(credential: Credential, user: User) -> Single { 31 | return auth.rx.createUser(withEmail: credential.email, password: credential.password) 32 | .flatMap { callback -> Single in 33 | let request = callback.user.createProfileChangeRequest() 34 | request.displayName = user.name 35 | 36 | return request.rx.commitChanges() 37 | .map { 38 | User( 39 | id: callback.user.uid, 40 | email: credential.email, 41 | name: user.name 42 | ) 43 | } 44 | } 45 | } 46 | func login(credential: Credential) -> Single { 47 | return auth.rx.signIn(withEmail: credential.email, password: credential.password) 48 | .map { 49 | User( 50 | id: $0.user.uid, 51 | email: credential.email, 52 | name: $0.user.displayName ?? "" 53 | ) 54 | } 55 | .do(onSuccess: { [weak self] user in 56 | self?.userRelay.accept(user) 57 | }) 58 | } 59 | func logout() -> Single { 60 | return auth.rx.signOut() 61 | .do(onSuccess: { [weak self] () in 62 | self?.userRelay.accept(nil) 63 | }) 64 | } 65 | 66 | // MARK: - Lifecycle 67 | 68 | required init(_ auth: Auth) { 69 | self.auth = auth 70 | 71 | if let user = auth.currentUser { 72 | userRelay.accept( 73 | User( 74 | id: user.uid, 75 | email: user.email ?? "", 76 | name: user.displayName ?? "" 77 | ) 78 | ) 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Services/Impl/ToastServiceImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToastServiceImpl.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import RxCocoa 11 | import RxSwift 12 | 13 | final class ToastServiceImpl: ToastService, Component { 14 | 15 | // MARK: - Property 16 | 17 | let event = PublishRelay() 18 | 19 | // MARK: - Public 20 | 21 | @discardableResult 22 | func show(_ message: String, level: Toast.Level) -> UUID { 23 | let toast = Toast(message: message, level: level, duration: 3) 24 | event.accept(toast) 25 | 26 | return toast.uuid 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Services/Impl/TodoServiceImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoServiceImpl.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import RxSwift 11 | 12 | final class TodoServiceImpl: TodoService, Autowired { 13 | 14 | // MARK: - Private 15 | 16 | private let todoRepository: TodoRepository 17 | private let authService: AuthService 18 | 19 | private let disposeBag = DisposeBag() 20 | 21 | private func bindEvents() { 22 | authService.observeUser() 23 | .distinctUntilChanged { (i, j) -> Bool in 24 | switch (i, j) { 25 | case (.none, .none): 26 | return true 27 | default: 28 | return false 29 | } 30 | } 31 | .scan((nil, nil)) { (old, new) -> (User?, User?) in 32 | return (old.1, new) 33 | } 34 | .filter { $0.1 == nil } 35 | .flatMapLatest { [weak self] (user, _) -> Observable in 36 | guard let ss = self else { return .empty() } 37 | guard let user = user else { return .empty() } 38 | return ss.todoRepository.clear(for: user).asObservable() 39 | } 40 | .subscribe() 41 | .disposed(by: disposeBag) 42 | } 43 | 44 | // MARK: - Public 45 | 46 | func get(id: String) -> Single { 47 | guard let user = authService.currentUser() else { 48 | return .error(TodoServiceError.userNotFound) 49 | } 50 | return todoRepository.get(id: id, for: user) 51 | } 52 | func gets() -> Single<[Todo]> { 53 | guard let user = authService.currentUser() else { 54 | return .error(TodoServiceError.userNotFound) 55 | } 56 | return todoRepository.gets(for: user) 57 | .catchErrorJustReturn([]) 58 | } 59 | 60 | func observe(id: String) -> Observable { 61 | guard let user = authService.currentUser() else { 62 | return .error(TodoServiceError.userNotFound) 63 | } 64 | return todoRepository.observe(id: id, for: user) 65 | } 66 | func observes() -> Observable<[Todo]> { 67 | guard let user = authService.currentUser() else { 68 | return .error(TodoServiceError.userNotFound) 69 | } 70 | return todoRepository.observes(for: user) 71 | .catchErrorJustReturn([]) 72 | } 73 | 74 | func insert(title: String) -> Single { 75 | guard let user = authService.currentUser() else { 76 | return .error(TodoServiceError.userNotFound) 77 | } 78 | return todoRepository.insert( 79 | Todo( 80 | id: UniqueManager.generate(), 81 | title: title, 82 | isChecked: false, 83 | createdAt: Date(), 84 | updatedAt: Date() 85 | ), 86 | for: user 87 | ) 88 | } 89 | func update(todo: Todo) -> Single { 90 | guard let user = authService.currentUser() else { 91 | return .error(TodoServiceError.userNotFound) 92 | } 93 | 94 | var todo = todo 95 | todo.updatedAt = Date() 96 | return todoRepository.update(todo, for: user) 97 | } 98 | func delete(id: String) -> Single { 99 | guard let user = authService.currentUser() else { 100 | return .error(TodoServiceError.userNotFound) 101 | } 102 | return todoRepository.delete(id: id, for: user) 103 | } 104 | 105 | // MARK: - Lifecycle 106 | 107 | required init(local todoRepository: TodoRepository, _ authService: AuthService) { 108 | self.todoRepository = todoRepository 109 | self.authService = authService 110 | 111 | bindEvents() 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Services/Impl/TodoSyncServiceImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoSyncServiceImpl.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import RxCocoa 11 | import RxSwift 12 | 13 | import NotificationBanner 14 | 15 | final class TodoSyncServiceImpl: SyncService, Autowired { 16 | 17 | // MARK: - Deli 18 | 19 | var qualifier: String? = "todo" 20 | var scope: Scope = .always 21 | 22 | // MARK: - Private 23 | 24 | private let localTodoRepository: TodoRepository 25 | private let remoteTodoRepository: TodoRepository 26 | private let authService: AuthService 27 | private let scheduler: SchedulerType 28 | 29 | private let disposeBag = DisposeBag() 30 | 31 | private func bindEvents() { 32 | authService.observeUser() 33 | .scan((authService.currentUser(), authService.currentUser())) { (old, new) -> (User?, User?) in 34 | return (old.1, new) 35 | } 36 | .flatMapLatest { [unowned self] users -> Observable in 37 | guard let user = users.1 else { return .empty() } 38 | guard users.0 == nil else { 39 | return self.localTodoRepository.clear(for: user) 40 | .asObservable() 41 | .map { user } 42 | } 43 | 44 | return self.remoteTodoRepository.gets(for: user) 45 | .asObservable() 46 | .flatMapLatest { list in 47 | return self.localTodoRepository.clear(for: user) 48 | .asObservable() 49 | .map { list } 50 | } 51 | .flatMapLatest { list -> Observable in 52 | return Observable.merge(list.map { todo -> Observable in 53 | self.localTodoRepository.insert(todo, for: user).asObservable().map { _ in Void() } 54 | }) 55 | } 56 | .map { user } 57 | } 58 | .flatMapLatest { [unowned self] user -> Observable<([Todo], [Todo])> in 59 | return Observable 60 | .combineLatest( 61 | self.remoteTodoRepository.observes(for: user), 62 | self.localTodoRepository.observes(for: user) 63 | ) 64 | } 65 | .flatMap { [unowned self] tuple -> Observable in 66 | guard let user = self.authService.currentUser() else { return .empty() } 67 | 68 | var oldDict = [String: Todo]() 69 | var newDict = [String: Todo]() 70 | 71 | for todo in tuple.0 { 72 | oldDict[todo.id] = todo 73 | } 74 | for todo in tuple.1 { 75 | newDict[todo.id] = todo 76 | } 77 | 78 | /// Add Todo 79 | for (id, value) in newDict where oldDict[id] == nil { 80 | return self.remoteTodoRepository.insert(value, for: user) 81 | .asObservable() 82 | .map { _ in Void() } 83 | } 84 | 85 | /// Update Todo 86 | for (id, value) in newDict { 87 | guard let old = oldDict[id] else { continue } 88 | guard old != value else { continue } 89 | return self.remoteTodoRepository.update(value, for: user).asObservable() 90 | } 91 | 92 | /// Delete Todo 93 | for (id, _) in oldDict where newDict[id] == nil { 94 | return self.remoteTodoRepository.delete(id: id, for: user).asObservable() 95 | } 96 | 97 | DispatchQueue.main.async { 98 | let banner = StatusBarNotificationBanner(title: "Sync complete", style: .success) 99 | banner.duration = 1 100 | banner.show() 101 | } 102 | return .empty() 103 | } 104 | .subscribe() 105 | .disposed(by: disposeBag) 106 | } 107 | 108 | // MARK: - Lifecycle 109 | 110 | required init( 111 | local localTodoRepository: TodoRepository, 112 | remote remoteTodoRepository: TodoRepository, 113 | _ authService: AuthService, 114 | sync scheduler: SchedulerType 115 | ) { 116 | self.localTodoRepository = localTodoRepository 117 | self.remoteTodoRepository = remoteTodoRepository 118 | self.authService = authService 119 | self.scheduler = scheduler 120 | 121 | bindEvents() 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Services/SyncService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SyncService.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | protocol SyncService {} 10 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Services/ToastService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToastService.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxCocoa 10 | import RxSwift 11 | 12 | protocol ToastService { 13 | var event: PublishRelay { get } 14 | 15 | @discardableResult 16 | func show(_ message: String, level: Toast.Level) -> UUID 17 | } 18 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Services/TodoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoService.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | enum TodoServiceError: Error { 12 | case userNotFound 13 | } 14 | 15 | protocol TodoService { 16 | func get(id: String) -> Single 17 | func gets() -> Single<[Todo]> 18 | 19 | func observe(id: String) -> Observable 20 | func observes() -> Observable<[Todo]> 21 | 22 | func insert(title: String) -> Single 23 | func update(todo: Todo) -> Single 24 | func delete(id: String) -> Single 25 | } 26 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/AddTodoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTodoViewController.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 07/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import Deli 12 | import ReactorKit 13 | import RxSwift 14 | import SnapKit 15 | 16 | import TextFieldEffects 17 | 18 | final class AddTodoViewController: UIViewController, View, Autowired { 19 | 20 | // MARK: - Deli 21 | 22 | var scope: Scope = .weak 23 | 24 | // MARK: - Property 25 | 26 | var disposeBag = DisposeBag() 27 | 28 | // MARK: - Interface 29 | 30 | private lazy var cancelButtonItem: UIBarButtonItem = { 31 | let item = UIBarButtonItem(title: "Cancel", style: .plain, target: nil, action: nil) 32 | return item 33 | }() 34 | private lazy var doneButtonItem: UIBarButtonItem = { 35 | let item = UIBarButtonItem(title: "Done", style: .done, target: nil, action: nil) 36 | return item 37 | }() 38 | 39 | private lazy var titleField: HoshiTextField = { 40 | let view = HoshiTextField() 41 | view.placeholder = "Title" 42 | view.placeholderFontScale = 1 43 | view.placeholderColor = .black 44 | view.borderInactiveColor = UIColor.black.withAlphaComponent(0.5) 45 | view.borderActiveColor = .black 46 | view.returnKeyType = .done 47 | view.textColor = .black 48 | return view 49 | }() 50 | 51 | private lazy var activityIndicatorView = UIActivityIndicatorView(style: .gray) 52 | 53 | // MARK: - Private 54 | 55 | private func setupConstraints() { 56 | titleField.snp.makeConstraints { maker in 57 | maker.topMargin.equalToSuperview().offset(20) 58 | maker.leading.trailing.equalToSuperview().inset(20) 59 | maker.height.equalTo(60) 60 | } 61 | activityIndicatorView.snp.makeConstraints { maker in 62 | maker.center.equalToSuperview() 63 | } 64 | } 65 | 66 | // MARK: - Public 67 | 68 | func bind(reactor: AddTodoViewReactor) { 69 | /// State 70 | reactor.state.map { $0.title.isNotEmpty } 71 | .distinctUntilChanged() 72 | .bind(to: doneButtonItem.rx.isEnabled) 73 | .disposed(by: disposeBag) 74 | 75 | reactor.state.map { $0.isLoading } 76 | .distinctUntilChanged() 77 | .asDriver(onErrorJustReturn: false) 78 | .drive(onNext: { [weak self] isLoading in 79 | if isLoading { 80 | self?.view.isUserInteractionEnabled = false 81 | self?.activityIndicatorView.startAnimating() 82 | } else { 83 | self?.view.isUserInteractionEnabled = true 84 | self?.activityIndicatorView.stopAnimating() 85 | } 86 | }) 87 | .disposed(by: disposeBag) 88 | 89 | reactor.state.map { $0.isCreated } 90 | .skip(1) 91 | .filter { $0 } 92 | .asDriver(onErrorJustReturn: false) 93 | .drive(onNext: { [weak self] _ in 94 | self?.dismiss(animated: true) 95 | }) 96 | .disposed(by: disposeBag) 97 | 98 | /// Action 99 | titleField.rx.text.map { Reactor.Action.setTitle($0 ?? "") } 100 | .bind(to: reactor.action) 101 | .disposed(by: disposeBag) 102 | 103 | cancelButtonItem.rx.tap 104 | .asDriver() 105 | .drive(onNext: { [weak self] () in 106 | self?.dismiss(animated: true) 107 | }) 108 | .disposed(by: disposeBag) 109 | 110 | doneButtonItem.rx.tap 111 | .map { Reactor.Action.create } 112 | .bind(to: reactor.action) 113 | .disposed(by: disposeBag) 114 | } 115 | 116 | override func viewDidLoad() { 117 | super.viewDidLoad() 118 | 119 | navigationItem.title = "Add Todo" 120 | navigationItem.leftBarButtonItem = cancelButtonItem 121 | navigationItem.rightBarButtonItem = doneButtonItem 122 | 123 | view.backgroundColor = .white 124 | view.addSubview(titleField) 125 | view.addSubview(activityIndicatorView) 126 | 127 | setupConstraints() 128 | } 129 | 130 | // MARK: - Lifecycle 131 | 132 | required init(_ reactor: AddTodoViewReactor) { 133 | defer { self.reactor = reactor } 134 | 135 | super.init(nibName: nil, bundle: nil) 136 | } 137 | required init?(coder aDecoder: NSCoder) { 138 | fatalError("init(coder:) has not been implemented") 139 | } 140 | deinit { 141 | logger.debug("deinit: \(type(of: self))") 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/AddTodoViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTodoViewReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 07/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | import RxValidator 13 | 14 | final class AddTodoViewReactor: Reactor, Autowired { 15 | 16 | // MARK: - Deli 17 | 18 | var scope: Scope = .prototype 19 | 20 | // MARK: - Private 21 | 22 | private let todoService: TodoService 23 | private let toastService: ToastService 24 | 25 | // MARK: - Reactor 26 | 27 | enum Action { 28 | case create 29 | 30 | case setTitle(String) 31 | } 32 | enum Mutation { 33 | case setTitle(String) 34 | 35 | case setLoading(Bool) 36 | case setCreated(Bool) 37 | } 38 | struct State { 39 | var title: String = "" 40 | 41 | var isLoading: Bool = false 42 | var isCreated: Bool = false 43 | } 44 | 45 | var initialState = State() 46 | 47 | func mutate(action: Action) -> Observable { 48 | switch action { 49 | case .create: 50 | let startLoading = Observable.just(Mutation.setLoading(true)) 51 | let addProcess = state.map { $0.title } 52 | .validate(.shouldNotBeEmpty, message: "Title is empty") 53 | .take(1) 54 | .flatMapLatest { [weak self] title -> Observable in 55 | guard let ss = self else { throw CommonError.nilSelf } 56 | return ss.todoService.insert(title: title).asObservable() 57 | } 58 | .map { _ in Mutation.setCreated(true) } 59 | .catchError { [weak self] error in 60 | switch error { 61 | case let RxValidatorResult.notValidWithMessage(message): 62 | self?.toastService.show(message, level: .normal) 63 | default: 64 | self?.toastService.show("Unknown error", level: .normal) 65 | } 66 | 67 | return .of( 68 | Mutation.setLoading(false), 69 | Mutation.setCreated(false) 70 | ) 71 | } 72 | 73 | return startLoading.concat(addProcess) 74 | 75 | case let .setTitle(title): 76 | return .just(.setTitle(title)) 77 | } 78 | } 79 | func reduce(state: State, mutation: Mutation) -> State { 80 | var state = state 81 | 82 | switch mutation { 83 | case let .setTitle(title): 84 | state.title = title 85 | case let .setLoading(isLoading): 86 | state.isLoading = isLoading 87 | case let .setCreated(isCreated): 88 | state.isCreated = isCreated 89 | } 90 | 91 | return state 92 | } 93 | 94 | // MARK: - Lifecycle 95 | 96 | required init(_ todoService: TodoService, _ toastService: ToastService) { 97 | self.todoService = todoService 98 | self.toastService = toastService 99 | } 100 | deinit { 101 | logger.debug("deinit: \(type(of: self))") 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/LoginViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | import RxValidator 13 | 14 | final class LoginViewReactor: Reactor, Autowired { 15 | 16 | // MARK: - Deli 17 | 18 | var scope: Scope = .prototype 19 | 20 | // MARK: - Private 21 | 22 | private let authService: AuthService 23 | private let toastService: ToastService 24 | private let analytics: AppAnalytics 25 | 26 | // MARK: - Reactor 27 | 28 | enum Action { 29 | case login 30 | case setEmail(String) 31 | case setPassword(String) 32 | } 33 | enum Mutation { 34 | case setEmail(String) 35 | case setPassword(String) 36 | case setLoading(Bool) 37 | case setLoggedIn(Bool) 38 | } 39 | struct State { 40 | var email: String = "" 41 | var password: String = "" 42 | var isLoading: Bool = false 43 | var isLoggedIn: Bool = false 44 | } 45 | 46 | var initialState = State() 47 | 48 | func mutate(action: Action) -> Observable { 49 | switch action { 50 | case .login: 51 | return Observable 52 | .combineLatest( 53 | state.map { $0.email } 54 | .validate(.shouldNotBeEmpty, message: "Empty email.") 55 | .validate(.isNotOverflowThen(max: 50), message: "Overflow email.") 56 | .validate(.shouldBeMatch("[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\\.[a-zA-Z]+"), message: "Not matched email pattern."), 57 | state.map { $0.password } 58 | .validate(.isNotUnderflowThen(min: 6), message: "Underflow password.") 59 | ) { Credential(email: $0, password: $1) } 60 | .take(1) 61 | .flatMapLatest { [weak self] credential -> Observable in 62 | guard let ss = self else { return .empty() } 63 | 64 | let setLoading = Observable.just(Mutation.setLoading(true)) 65 | let loginProcess = ss.authService.login(credential: credential) 66 | .asObservable() 67 | .map { _ in Mutation.setLoggedIn(true) } 68 | .catchError { [weak self] _ in 69 | self?.toastService.show("Login failed", level: .normal) 70 | return .of( 71 | Mutation.setLoading(false), 72 | Mutation.setLoggedIn(false) 73 | ) 74 | } 75 | 76 | return setLoading.concat(loginProcess) 77 | } 78 | .catchError { [weak self] error in 79 | switch error { 80 | case let RxValidatorResult.notValidWithMessage(message): 81 | self?.toastService.show(message, level: .normal) 82 | default: 83 | self?.analytics.log(.loginUnknownError(message: error.localizedDescription)) 84 | self?.toastService.show("Login failed", level: .normal) 85 | } 86 | return .just(.setLoggedIn(false)) 87 | } 88 | 89 | case let .setEmail(email): 90 | return .just(Mutation.setEmail(email)) 91 | 92 | case let .setPassword(password): 93 | return .just(Mutation.setPassword(password)) 94 | } 95 | } 96 | 97 | func reduce(state: State, mutation: Mutation) -> State { 98 | var state = state 99 | 100 | switch mutation { 101 | case let .setEmail(email): 102 | state.email = email 103 | 104 | case let .setPassword(password): 105 | state.password = password 106 | 107 | case let .setLoading(isLoading): 108 | state.isLoading = isLoading 109 | 110 | case let .setLoggedIn(isLoggedIn): 111 | state.isLoggedIn = isLoggedIn 112 | } 113 | 114 | return state 115 | } 116 | 117 | // MARK: - Lifecycle 118 | 119 | required init(_ authService: AuthService, _ toastService: ToastService, _ analytics: AppAnalytics) { 120 | self.authService = authService 121 | self.toastService = toastService 122 | self.analytics = analytics 123 | } 124 | deinit { 125 | logger.debug("deinit: \(type(of: self))") 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/MainNavigationViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainNavigationViewReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 05/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | 13 | final class MainNavigationViewReactor: Reactor, Component { 14 | 15 | // MARK: - Deli 16 | 17 | var scope: Scope = .prototype 18 | 19 | // MARK: - Enumerable 20 | 21 | enum Tab: Int, CaseIterable, Equatable { 22 | case todo 23 | case more 24 | } 25 | 26 | // MARK: - Reactor 27 | 28 | enum Action { 29 | case changeTab(Tab) 30 | } 31 | enum Mutation { 32 | case changeTab(Tab) 33 | } 34 | struct State { 35 | var currentTab: Tab = .todo 36 | } 37 | 38 | var initialState = State() 39 | 40 | func mutate(action: Action) -> Observable { 41 | switch action { 42 | case let .changeTab(tab): 43 | return .just(.changeTab(tab)) 44 | } 45 | } 46 | func reduce(state: State, mutation: Mutation) -> State { 47 | var state = state 48 | 49 | switch mutation { 50 | case let .changeTab(tab): 51 | state.currentTab = tab 52 | } 53 | 54 | return state 55 | } 56 | 57 | // MARK: - Lifecycle 58 | 59 | init() {} 60 | deinit { 61 | logger.debug("deinit: \(type(of: self))") 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/MoreViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoreViewController.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 07/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import Deli 12 | import ReactorKit 13 | import RxDataSources 14 | import RxSwift 15 | 16 | final class MoreViewController: UIViewController, View, Autowired { 17 | 18 | // MARK: - Deli 19 | 20 | var scope: Scope = .weak 21 | 22 | // MARK: - Property 23 | 24 | var disposeBag = DisposeBag() 25 | 26 | // MARK: - Interface 27 | 28 | private lazy var tableView: UITableView = { 29 | let view = UITableView() 30 | view.register(MoreCell.self, forCellReuseIdentifier: "cell") 31 | 32 | view.contentInset.bottom = 49 33 | view.scrollIndicatorInsets.bottom = 49 34 | return view 35 | }() 36 | 37 | // MARK: - Private 38 | 39 | private let navigator: Navigator 40 | private let analytics: AppAnalytics 41 | 42 | private lazy var dataSource: RxTableViewSectionedReloadDataSource = { 43 | let dataSource = RxTableViewSectionedReloadDataSource(configureCell: { (_, tableView, _, item) in 44 | switch item { 45 | case .row(let reactor): 46 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? MoreCell else { 47 | return MoreCell() 48 | } 49 | cell.reactor = reactor 50 | 51 | return cell 52 | } 53 | }) 54 | dataSource.titleForHeaderInSection = { [weak self] (dataSource, index) in 55 | guard let section = self?.reactor?.currentState.sections[index] else { return nil } 56 | guard case .items(_, title: let title) = section else { return nil } 57 | return title 58 | } 59 | return dataSource 60 | }() 61 | 62 | private func setupConstraints() { 63 | tableView.snp.makeConstraints { maker in 64 | maker.edges.equalToSuperview() 65 | } 66 | } 67 | 68 | // MARK: - Public 69 | 70 | func bind(reactor: MoreViewReactor) { 71 | /// State 72 | reactor.state.map { $0.sections } 73 | .asDriver(onErrorJustReturn: []) 74 | .drive(tableView.rx.items(dataSource: dataSource)) 75 | .disposed(by: disposeBag) 76 | 77 | reactor.state.map { $0.isLoggedOut } 78 | .asDriver(onErrorJustReturn: false) 79 | .filter { $0 } 80 | .drive(onNext: { [weak self] _ in 81 | self?.navigator.navigate(to: .login) 82 | }) 83 | .disposed(by: disposeBag) 84 | 85 | /// Action 86 | tableView.rx.itemSelected 87 | .flatMapLatest { indexPath -> Observable in 88 | switch indexPath.item { 89 | case 0: 90 | return .just(.logout) 91 | default: 92 | return .empty() 93 | } 94 | } 95 | .do(onNext: { [weak self] action in 96 | switch action { 97 | case .logout: 98 | self?.analytics.log(.moreLogout) 99 | } 100 | }) 101 | .bind(to: reactor.action) 102 | .disposed(by: disposeBag) 103 | } 104 | 105 | override func viewDidLoad() { 106 | super.viewDidLoad() 107 | 108 | navigationItem.title = "More" 109 | view.addSubview(tableView) 110 | 111 | setupConstraints() 112 | } 113 | 114 | // MARK: - Lifecycle 115 | 116 | required init(_ reactor: MoreViewReactor, _ navigator: Navigator, _ analytics: AppAnalytics) { 117 | defer { self.reactor = reactor } 118 | self.navigator = navigator 119 | self.analytics = analytics 120 | 121 | super.init(nibName: nil, bundle: nil) 122 | } 123 | required init?(coder aDecoder: NSCoder) { 124 | fatalError("init(coder:) has not been implemented") 125 | } 126 | deinit { 127 | logger.debug("deinit: \(type(of: self))") 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/MoreViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoreViewReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 07/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | 13 | final class MoreViewReactor: Reactor, Autowired { 14 | 15 | // MARK: - Deli 16 | 17 | var scope: Scope = .prototype 18 | 19 | // MARK: - Private 20 | 21 | private let authService: AuthService 22 | 23 | // MARK: - Reactor 24 | 25 | enum Action { 26 | case logout 27 | } 28 | enum Mutation { 29 | case setLoggedOut(Bool) 30 | } 31 | struct State { 32 | var sections: [MoreSection] = [ 33 | .items([ 34 | .row(Inject(MoreCellReactor.self, with: ("Logout"))) 35 | ], title: "") 36 | ] 37 | var isLoggedOut: Bool = false 38 | } 39 | 40 | var initialState = State() 41 | 42 | func mutate(action: Action) -> Observable { 43 | switch action { 44 | case .logout: 45 | return authService.logout() 46 | .asObservable() 47 | .map { Mutation.setLoggedOut(true) } 48 | .catchError { _ in .just(.setLoggedOut(false)) } 49 | } 50 | } 51 | func reduce(state: State, mutation: Mutation) -> State { 52 | var state = state 53 | 54 | switch mutation { 55 | case let .setLoggedOut(isLoggedOut): 56 | state.isLoggedOut = isLoggedOut 57 | } 58 | 59 | return state 60 | } 61 | 62 | // MARK: - Lifecycle 63 | 64 | required init(_ authService: AuthService) { 65 | self.authService = authService 66 | } 67 | deinit { 68 | logger.debug("deinit: \(type(of: self))") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/SignUpViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignUpViewReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 04/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | import RxValidator 13 | 14 | final class SignUpViewReactor: Reactor, Autowired { 15 | 16 | // MARK: - Deli 17 | 18 | var scope: Scope = .prototype 19 | 20 | // MARK: - Private 21 | 22 | private let authService: AuthService 23 | private let toastService: ToastService 24 | private let analytics: AppAnalytics 25 | 26 | // MARK: - Reactor 27 | 28 | enum Action { 29 | case setEmail(String) 30 | case setPassword(String) 31 | case setName(String) 32 | 33 | case signUp 34 | } 35 | enum Mutation { 36 | case setEmail(String) 37 | case setPassword(String) 38 | case setName(String) 39 | 40 | case setLoading(Bool) 41 | case setSignedUp(Bool) 42 | } 43 | struct State { 44 | var email: String = "" 45 | var password: String = "" 46 | var name: String = "" 47 | 48 | var isLoading: Bool = false 49 | var isSignedUp: Bool = false 50 | } 51 | 52 | var initialState = State() 53 | 54 | func mutate(action: Action) -> Observable { 55 | switch action { 56 | case .signUp: 57 | return Observable 58 | .combineLatest( 59 | state.map { $0.email } 60 | .validate(.shouldNotBeEmpty, message: "Empty email.") 61 | .validate(.isNotOverflowThen(max: 50), message: "Overflow email.") 62 | .validate(.shouldBeMatch("[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\\.[a-zA-Z]+"), message: "Not matched email pattern."), 63 | state.map { $0.password } 64 | .validate(.isNotUnderflowThen(min: 6), message: "Underflow password."), 65 | state.map { $0.name } 66 | .validate(.shouldNotBeEmpty, message: "Empty name.") 67 | ) { (Credential(email: $0, password: $1), $2) } 68 | .take(1) 69 | .flatMapLatest { [weak self] (credential, name) -> Observable in 70 | guard let ss = self else { return .empty() } 71 | 72 | let user = User( 73 | id: "", 74 | email: credential.email, 75 | name: name 76 | ) 77 | 78 | let setLoading = Observable.just(Mutation.setLoading(true)) 79 | let signUpProcess = ss.authService.signUp(credential: credential, user: user) 80 | .asObservable() 81 | .map { _ in Mutation.setSignedUp(true) } 82 | .catchError { [weak self] _ in 83 | self?.toastService.show("Sign-up failed", level: .normal) 84 | return .of( 85 | Mutation.setLoading(false), 86 | Mutation.setSignedUp(false) 87 | ) 88 | } 89 | 90 | return setLoading.concat(signUpProcess) 91 | } 92 | .catchError { [weak self] error in 93 | switch error { 94 | case let RxValidatorResult.notValidWithMessage(message): 95 | self?.toastService.show(message, level: .normal) 96 | default: 97 | self?.analytics.log(.signUpUnknownError(message: error.localizedDescription)) 98 | self?.toastService.show("Sign-up failed", level: .normal) 99 | } 100 | return .just(.setSignedUp(false)) 101 | } 102 | 103 | case let .setEmail(email): 104 | return .just(Mutation.setEmail(email)) 105 | 106 | case let .setPassword(password): 107 | return .just(Mutation.setPassword(password)) 108 | 109 | case let .setName(name): 110 | return .just(Mutation.setName(name)) 111 | } 112 | } 113 | func reduce(state: State, mutation: Mutation) -> State { 114 | var state = state 115 | 116 | switch mutation { 117 | case let .setEmail(email): 118 | state.email = email 119 | 120 | case let .setPassword(password): 121 | state.password = password 122 | 123 | case let .setName(name): 124 | state.name = name 125 | 126 | case let .setLoading(isLoading): 127 | state.isLoading = isLoading 128 | 129 | case let .setSignedUp(isSignedUp): 130 | state.isSignedUp = isSignedUp 131 | } 132 | 133 | return state 134 | } 135 | 136 | // MARK: - Lifecycle 137 | 138 | required init(_ authService: AuthService, _ toastService: ToastService, _ analytics: AppAnalytics) { 139 | self.authService = authService 140 | self.toastService = toastService 141 | self.analytics = analytics 142 | } 143 | deinit { 144 | logger.debug("deinit: \(type(of: self))") 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/SplashViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashViewController.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import Deli 12 | import ReactorKit 13 | import RxSwift 14 | import SnapKit 15 | import Gradients 16 | 17 | final class SplashViewController: UIViewController, View, Autowired { 18 | 19 | // MARK: - Deli 20 | 21 | var scope: Scope = .weak 22 | 23 | // MARK: - Property 24 | 25 | var disposeBag = DisposeBag() 26 | 27 | // MARK: - Interface 28 | 29 | private lazy var backgroundLayer = Gradients.nightSky.layer() 30 | 31 | private lazy var appLabel: UILabel = { 32 | let label = UILabel() 33 | label.font = UIFont.systemFont(ofSize: 36, weight: .bold) 34 | label.text = "Deli Todo" 35 | label.textColor = .white 36 | return label 37 | }() 38 | private lazy var activityIndicatorView = UIActivityIndicatorView(style: .white) 39 | 40 | // MARK: - Private 41 | 42 | private let navigator: Navigator 43 | 44 | private func setupConstraints() { 45 | appLabel.snp.makeConstraints { maker in 46 | maker.center.equalToSuperview() 47 | } 48 | activityIndicatorView.snp.makeConstraints { maker in 49 | maker.centerX.equalToSuperview() 50 | maker.top.equalTo(appLabel.snp.bottom).offset(20) 51 | } 52 | } 53 | 54 | // MARK: - Public 55 | 56 | func bind(reactor: SplashViewReactor) { 57 | /// State 58 | reactor.state.map { $0.isAuthenticated } 59 | .skip(1) 60 | .distinctUntilChanged() 61 | .subscribe(onNext: { [weak self] isAuthenticated in 62 | if !isAuthenticated { 63 | self?.navigator.navigate(to: .login) 64 | } else { 65 | self?.navigator.navigate(to: .main) 66 | } 67 | }) 68 | .disposed(by: self.disposeBag) 69 | 70 | /// Action 71 | rx.viewDidAppear 72 | .take(1) 73 | .map { _ in Reactor.Action.checkAuthenticated } 74 | .bind(to: reactor.action) 75 | .disposed(by: disposeBag) 76 | } 77 | 78 | override func viewDidLoad() { 79 | super.viewDidLoad() 80 | 81 | view.layer.addSublayer(backgroundLayer) 82 | 83 | view.addSubview(appLabel) 84 | view.addSubview(activityIndicatorView) 85 | 86 | activityIndicatorView.startAnimating() 87 | 88 | setupConstraints() 89 | } 90 | override func viewDidLayoutSubviews() { 91 | super.viewDidLayoutSubviews() 92 | 93 | backgroundLayer.frame = view.bounds 94 | } 95 | 96 | // MARK: - Lifecycle 97 | 98 | required init(_ reactor: SplashViewReactor, _ navigator: Navigator) { 99 | defer { self.reactor = reactor } 100 | self.navigator = navigator 101 | 102 | super.init(nibName: nil, bundle: nil) 103 | } 104 | required init?(coder aDecoder: NSCoder) { 105 | fatalError("init(coder:) has not been implemented") 106 | } 107 | deinit { 108 | logger.debug("deinit: \(type(of: self))") 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/SplashViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashViewReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 03/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | import RxOptional 13 | 14 | final class SplashViewReactor: Reactor, Autowired { 15 | 16 | // MARL: - Deli 17 | 18 | var scope: Scope = .prototype 19 | 20 | // MARK: - Private 21 | 22 | private let authService: AuthService 23 | 24 | // MARK: - Reactor 25 | 26 | enum Action { 27 | case checkAuthenticated 28 | } 29 | enum Mutation { 30 | case setAuthenticated(Bool) 31 | } 32 | struct State { 33 | var isAuthenticated: Bool = false 34 | } 35 | 36 | var initialState = State() 37 | 38 | func mutate(action: Action) -> Observable { 39 | switch action { 40 | case .checkAuthenticated: 41 | return Observable.just(authService.currentUser()) 42 | .map { $0 != nil } 43 | .map(Mutation.setAuthenticated) 44 | } 45 | } 46 | func reduce(state: State, mutation: Mutation) -> State { 47 | var state = state 48 | 49 | switch mutation { 50 | case let .setAuthenticated(isAuthenticated): 51 | state.isAuthenticated = isAuthenticated 52 | } 53 | 54 | return state 55 | } 56 | 57 | // MARK: - Lifecycle 58 | 59 | required init(_ authService: AuthService) { 60 | self.authService = authService 61 | } 62 | deinit { 63 | logger.debug("deinit: \(type(of: self))") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/ToastViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToastViewController.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import Deli 12 | import ReactorKit 13 | import RxSwift 14 | import SnapKit 15 | 16 | final class ToastViewController: UIViewController, View, Autowired { 17 | 18 | // MARK: - Constant 19 | 20 | private struct Constant { 21 | static let toastMargin: CGFloat = 10 22 | } 23 | 24 | // MARK: - Deli 25 | 26 | var scope: Scope = .weak 27 | 28 | // MARK: - Property 29 | 30 | var disposeBag = DisposeBag() 31 | 32 | // MARK: - Interface 33 | 34 | private lazy var toastBaseView: UIView = { 35 | let view = UIView() 36 | view.backgroundColor = .clear 37 | return view 38 | }() 39 | 40 | // MARK: - Private 41 | 42 | private var toastDict: [UUID: ToastView] = [:] 43 | 44 | private func setupConstraints() { 45 | toastBaseView.snp.makeConstraints { maker in 46 | maker.edges.equalToSuperview() 47 | } 48 | } 49 | 50 | private func showToast(_ toast: Toast) { 51 | let toastView = Inject(ToastView.self, with: (toast)) 52 | 53 | let size = toastView.sizeThatFits(CGSize(width: view.bounds.width - 40, height: .greatestFiniteMagnitude)) 54 | toastView.frame = CGRect(x: (view.bounds.width - size.width) / 2, y: view.bounds.height + Constant.toastMargin, width: size.width, height: size.height) 55 | toastBaseView.addSubview(toastView) 56 | toastDict[toast.uuid] = toastView 57 | 58 | UIView.beginAnimations(nil, context: nil) 59 | UIView.setAnimationDuration(0.35) 60 | 61 | for toast in toastDict.values { 62 | if toast === toastView { 63 | toast.frame.origin.y -= (size.height + view.safeAreaInsets.bottom + Constant.toastMargin) 64 | } else { 65 | toast.frame.origin.y -= (size.height + Constant.toastMargin) 66 | } 67 | } 68 | 69 | UIView.commitAnimations() 70 | } 71 | private func hideToast(_ toast: Toast) { 72 | guard let view = toastDict[toast.uuid] else { return } 73 | UIView.animate(withDuration: 0.35, animations: { 74 | view.alpha = 0 75 | }, completion: { [weak self] _ in 76 | view.removeFromSuperview() 77 | self?.toastDict.removeValue(forKey: toast.uuid) 78 | }) 79 | } 80 | 81 | // MARK: - Public 82 | 83 | func bind(reactor: ToastViewReactor) { 84 | /// State 85 | reactor.state.map { $0.event } 86 | .distinctUntilChanged { $0 == $1 } 87 | .asDriver(onErrorJustReturn: .none) 88 | .drive(onNext: { [weak self] event in 89 | switch event { 90 | case let .show(toast): 91 | self?.showToast(toast) 92 | case let .hide(toast): 93 | self?.hideToast(toast) 94 | case .none: 95 | break 96 | } 97 | }) 98 | .disposed(by: disposeBag) 99 | 100 | /// Action 101 | reactor.action.onNext(.setRunning(true)) 102 | } 103 | 104 | override func viewDidLoad() { 105 | super.viewDidLoad() 106 | 107 | view.addSubview(toastBaseView) 108 | 109 | setupConstraints() 110 | } 111 | 112 | // MARK: - Lifecycle 113 | 114 | required init(_ reactor: ToastViewReactor) { 115 | defer { self.reactor = reactor } 116 | 117 | super.init(nibName: nil, bundle: nil) 118 | } 119 | required init?(coder aDecoder: NSCoder) { 120 | fatalError("init(coder:) has not been implemented") 121 | } 122 | deinit { 123 | logger.debug("deinit: \(type(of: self))") 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/ToastViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToastViewReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 2018. 10. 4.. 6 | // Copyright © 2018년 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | 13 | final class ToastViewReactor: Reactor, Autowired { 14 | 15 | // MARK: - Enumerable 16 | 17 | enum ToastEvent: Equatable { 18 | case none 19 | case show(Toast) 20 | case hide(Toast) 21 | } 22 | 23 | // MARK: - Deli 24 | 25 | var scope: Scope = .prototype 26 | 27 | // MARK: - Private 28 | 29 | private let toastService: ToastService 30 | 31 | // MARK: - Reactor 32 | 33 | enum Action { 34 | case initial 35 | case setRunning(Bool) 36 | } 37 | enum Mutation { 38 | case setRunning(Bool) 39 | case showToast(Toast) 40 | case hideToast(Toast) 41 | } 42 | struct State { 43 | var event: ToastEvent = .none 44 | 45 | var isRunning: Bool = false 46 | var toastList: [Toast] = [] 47 | } 48 | 49 | var initialState = State() 50 | 51 | func mutate(action: Action) -> Observable { 52 | switch action { 53 | case .initial: 54 | return state.map { $0.isRunning } 55 | .distinctUntilChanged() 56 | .flatMapLatest { [toastService] isRunning -> Observable in 57 | if isRunning { 58 | return toastService.event.asObservable() 59 | } else { 60 | return .empty() 61 | } 62 | } 63 | .flatMap { toast -> Observable in 64 | let showToast = Observable.just(Mutation.showToast(toast)) 65 | let hideToast = Observable.just(Mutation.hideToast(toast)) 66 | .delay(TimeInterval(toast.duration), scheduler: MainScheduler.instance) 67 | 68 | return showToast.concat(hideToast) 69 | } 70 | 71 | case let .setRunning(isRunning): 72 | return .just(Mutation.setRunning(isRunning)) 73 | } 74 | } 75 | func reduce(state: State, mutation: Mutation) -> State { 76 | var state = state 77 | 78 | switch mutation { 79 | case let .setRunning(isRunning): 80 | state.isRunning = isRunning 81 | 82 | case let .showToast(toast): 83 | logger.debug("showToast(\(toast.uuid)): \(toast.message)") 84 | 85 | state.event = .show(toast) 86 | state.toastList.append(toast) 87 | 88 | case let .hideToast(toast): 89 | logger.debug("hideToast(\(toast.uuid)): \(toast.message)") 90 | 91 | state.event = .hide(toast) 92 | state.toastList.removeAll { $0.uuid == toast.uuid } 93 | } 94 | 95 | return state 96 | } 97 | 98 | // MARK: - Lifecycle 99 | 100 | required init(_ toastService: ToastService) { 101 | self.toastService = toastService 102 | 103 | action.onNext(.initial) 104 | } 105 | deinit { 106 | logger.debug("deinit: \(type(of: self))") 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /DeliTodo/Sources/ViewControllers/TodoViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoViewReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 05/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | 13 | final class TodoViewReactor: Reactor, Autowired { 14 | 15 | // MARK: - Deli 16 | 17 | var scope: Scope = .prototype 18 | 19 | // MARK: - Private 20 | 21 | private let todoService: TodoService 22 | 23 | // MARK: - Reactor 24 | 25 | enum Action { 26 | case initial 27 | 28 | case delete(id: String) 29 | } 30 | enum Mutation { 31 | case updateSections([TodoSection]) 32 | } 33 | struct State { 34 | var sections: [TodoSection] = [] 35 | } 36 | 37 | var initialState = State() 38 | 39 | func mutate(action: Action) -> Observable { 40 | switch action { 41 | case .initial: 42 | return todoService.observes() 43 | .map { [weak self] todoList -> [TodoItem] in 44 | guard let ss = self else { return [] } 45 | return todoList 46 | .sorted(by: { (cell1, cell2) -> Bool in 47 | return cell1.createdAt > cell2.createdAt 48 | }) 49 | .map { ss.Inject(TodoCellReactor.self, with: ($0)) } 50 | .map { TodoItem.row($0) } 51 | } 52 | .map { .updateSections([TodoSection.items($0)]) } 53 | 54 | case let .delete(id): 55 | return todoService.delete(id: id) 56 | .asObservable() 57 | .flatMapLatest { Observable.empty() } 58 | } 59 | } 60 | func reduce(state: State, mutation: Mutation) -> State { 61 | var state = state 62 | 63 | switch mutation { 64 | case let .updateSections(sections): 65 | state.sections = sections 66 | } 67 | 68 | return state 69 | } 70 | 71 | // MARK: - Lifecycle 72 | 73 | required init(_ todoService: TodoService) { 74 | self.todoService = todoService 75 | } 76 | deinit { 77 | logger.debug("deinit: \(type(of: self))") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Views/MoreCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoreCell.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 07/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import ReactorKit 12 | import RxSwift 13 | 14 | final class MoreCell: UITableViewCell, View { 15 | 16 | // MARK: - Property 17 | 18 | var disposeBag = DisposeBag() 19 | 20 | // MARK: - Public 21 | 22 | func bind(reactor: MoreCellReactor) { 23 | /// State 24 | reactor.state.map { $0.title } 25 | .bind(to: textLabel!.rx.text) 26 | .disposed(by: disposeBag) 27 | } 28 | 29 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 30 | super.init(style: style, reuseIdentifier: reuseIdentifier) 31 | 32 | accessoryType = .disclosureIndicator 33 | } 34 | required init?(coder aDecoder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | deinit { 38 | logger.debug("deinit: \(type(of: self))") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Views/MoreCellReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoreCellReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 07/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | 13 | class MoreCellPayload: Payload { 14 | let title: String 15 | 16 | required init(with argument: (String)) { 17 | self.title = argument 18 | } 19 | } 20 | 21 | final class MoreCellReactor: Reactor, AutowiredFactory { 22 | 23 | // MARK: - Reactor 24 | 25 | typealias Action = NoAction 26 | typealias Mutation = NoMutation 27 | struct State { 28 | var title: String = "" 29 | } 30 | 31 | var initialState: State 32 | 33 | // MARK: - Lifecycle 34 | 35 | required init(payload: MoreCellPayload) { 36 | initialState = State(title: payload.title) 37 | } 38 | deinit { 39 | logger.debug("deinit: \(type(of: self))") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Views/ToastView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToastView.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 08/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import Deli 12 | import RxSwift 13 | 14 | final class ToastPayload: Payload { 15 | let toast: Toast 16 | 17 | required init(with argument: (Toast)) { 18 | self.toast = argument 19 | } 20 | } 21 | 22 | final class ToastView: UIView, AutowiredFactory { 23 | 24 | // MARK: - Deli 25 | 26 | var scope: Scope = .prototype 27 | 28 | // MARK: - Public 29 | 30 | var disposeBag = DisposeBag() 31 | 32 | // MARK: - Interface 33 | 34 | private lazy var label: UILabel = { 35 | let label = UILabel() 36 | label.numberOfLines = 0 37 | label.textColor = .white 38 | label.font = UIFont.systemFont(ofSize: 16) 39 | return label 40 | }() 41 | 42 | // MARK: - Private 43 | 44 | private let toast: Toast 45 | 46 | private func setupConstraints() { 47 | label.snp.makeConstraints { maker in 48 | maker.leading.trailing.equalToSuperview().inset(12) 49 | maker.top.bottom.equalToSuperview().inset(8) 50 | } 51 | } 52 | 53 | // MARK: - Public 54 | 55 | override func sizeThatFits(_ size: CGSize) -> CGSize { 56 | let size = label.sizeThatFits( 57 | CGSize(width: size.width - 24, height: size.height) 58 | ) 59 | return CGSize( 60 | width: size.width + 24, 61 | height: size.height + 16 62 | ) 63 | } 64 | 65 | // MARK: - Lifecycle 66 | 67 | required init(payload: ToastPayload) { 68 | toast = payload.toast 69 | 70 | super.init(frame: .zero) 71 | 72 | backgroundColor = UIColor.black.withAlphaComponent(0.8) 73 | clipsToBounds = true 74 | layer.cornerRadius = 6 75 | 76 | addSubview(label) 77 | label.text = toast.message 78 | 79 | setupConstraints() 80 | } 81 | required init?(coder aDecoder: NSCoder) { 82 | fatalError("init(coder:) has not been implemented") 83 | } 84 | deinit { 85 | logger.debug("deinit: \(type(of: self))") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /DeliTodo/Sources/Views/TodoCellReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoCellReactor.swift 3 | // DeliTodo 4 | // 5 | // Created by Kawoou on 05/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | import ReactorKit 11 | import RxSwift 12 | 13 | final class TodoCellPayload: Payload { 14 | let todo: Todo 15 | 16 | required init(with argument: (Todo)) { 17 | self.todo = argument 18 | } 19 | } 20 | 21 | final class TodoCellReactor: Reactor, AutowiredFactory { 22 | 23 | // MARK: - Private 24 | 25 | private let todoService: TodoService 26 | 27 | // MARK: - Reactor 28 | 29 | enum Action { 30 | case setPressing(Bool) 31 | case setChecked(Bool) 32 | } 33 | enum Mutation { 34 | case setPressing(Bool) 35 | case setChecked(Bool) 36 | } 37 | struct State { 38 | var id: String = "" 39 | var title: String = "" 40 | var isPressing: Bool = false 41 | var isChecked: Bool = false 42 | } 43 | 44 | var initialState: State 45 | 46 | func mutate(action: Action) -> Observable { 47 | switch action { 48 | case let .setPressing(isPressing): 49 | return .just(.setPressing(isPressing)) 50 | case let .setChecked(isChecked): 51 | return todoService.get(id: currentState.id) 52 | .asObservable() 53 | .map { todo -> Todo in 54 | var todo = todo 55 | todo.isChecked = isChecked 56 | return todo 57 | } 58 | .flatMapLatest { [weak self] todo -> Observable in 59 | guard let ss = self else { throw CommonError.nilSelf } 60 | return ss.todoService.update(todo: todo).asObservable() 61 | } 62 | .map { _ in Mutation.setChecked(isChecked) } 63 | .asObservable() 64 | } 65 | } 66 | func reduce(state: State, mutation: Mutation) -> State { 67 | var state = state 68 | 69 | switch mutation { 70 | case let .setPressing(isPressing): 71 | state.isPressing = isPressing 72 | case let .setChecked(isChecked): 73 | state.isChecked = isChecked 74 | } 75 | 76 | return state 77 | } 78 | 79 | // MARK: - Lifecycle 80 | 81 | required init(_ todoService: TodoService, payload: TodoCellPayload) { 82 | self.todoService = todoService 83 | 84 | initialState = State( 85 | id: payload.todo.id, 86 | title: payload.todo.title, 87 | isPressing: false, 88 | isChecked: payload.todo.isChecked 89 | ) 90 | } 91 | deinit { 92 | logger.debug("deinit: \(type(of: self))") 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /DeliTodo/Supporting Files/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AD_UNIT_ID_FOR_BANNER_TEST 6 | ca-app-pub-3940256099942544/2934735716 7 | AD_UNIT_ID_FOR_INTERSTITIAL_TEST 8 | ca-app-pub-3940256099942544/4411468910 9 | CLIENT_ID 10 | 575441369002-9behp292ah8b8uu7gr371bse2a6c8qq6.apps.googleusercontent.com 11 | REVERSED_CLIENT_ID 12 | com.googleusercontent.apps.575441369002-9behp292ah8b8uu7gr371bse2a6c8qq6 13 | API_KEY 14 | AIzaSyDDTttP5B6CdE70RD6cpA4AckzLRoVJJa0 15 | GCM_SENDER_ID 16 | 575441369002 17 | PLIST_VERSION 18 | 1 19 | BUNDLE_ID 20 | io.kawoou.DeliTodo 21 | PROJECT_ID 22 | delitodo-bf972 23 | STORAGE_BUCKET 24 | delitodo-bf972.appspot.com 25 | IS_ADS_ENABLED 26 | 27 | IS_ANALYTICS_ENABLED 28 | 29 | IS_APPINVITE_ENABLED 30 | 31 | IS_GCM_ENABLED 32 | 33 | IS_SIGNIN_ENABLED 34 | 35 | GOOGLE_APP_ID 36 | 1:575441369002:ios:1f62e8bfd0def54d 37 | DATABASE_URL 38 | https://delitodo-bf972.firebaseio.com 39 | 40 | -------------------------------------------------------------------------------- /DeliTodo/Supporting Files/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 | Viewer 24 | CFBundleURLName 25 | io.kawoou.DeliTodo 26 | CFBundleURLSchemes 27 | 28 | deli-todo 29 | 30 | 31 | 32 | CFBundleVersion 33 | 1 34 | Fabric 35 | 36 | APIKey 37 | f2a114aca89951eb5883f5afab7e6cff50b70214 38 | Kits 39 | 40 | 41 | KitInfo 42 | 43 | KitName 44 | Crashlytics 45 | 46 | 47 | 48 | LSRequiresIPhoneOS 49 | 50 | UILaunchStoryboardName 51 | Launch Screen 52 | UIRequiredDeviceCapabilities 53 | 54 | armv7 55 | 56 | UISupportedInterfaceOrientations 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationLandscapeLeft 60 | UIInterfaceOrientationLandscapeRight 61 | 62 | UISupportedInterfaceOrientations~ipad 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationPortraitUpsideDown 66 | UIInterfaceOrientationLandscapeLeft 67 | UIInterfaceOrientationLandscapeRight 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Configuration/DeliConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeliConfiguration.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | import Deli 13 | 14 | final class DeliConfiguration: QuickConfiguration { 15 | override class func configure(_ configuration: Quick.Configuration) { 16 | configuration.beforeEach { 17 | testModule.unload() 18 | 19 | AppContext.shared.reset() 20 | AppContext.shared.load(testModule, priority: .high) 21 | } 22 | configuration.afterEach { 23 | AppContext.shared.unload(testModule) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Dummies/DummyTodo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DummyTodo.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @testable import DeliTodo 12 | 13 | struct DummyTodo { 14 | static var nonChecked: Todo { 15 | return Todo( 16 | id: "id1", 17 | title: "Todo 1", 18 | isChecked: false, 19 | createdAt: Date(timeIntervalSince1970: 10000), 20 | updatedAt: Date(timeIntervalSince1970: 20000) 21 | ) 22 | } 23 | static var checked: Todo { 24 | return Todo( 25 | id: "id2", 26 | title: "Todo 2", 27 | isChecked: true, 28 | createdAt: Date(timeIntervalSince1970: 20000), 29 | updatedAt: Date(timeIntervalSince1970: 30000) 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Dummies/DummyUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DummyUser.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | @testable import DeliTodo 10 | 11 | struct DummyUser { 12 | static var user: User { 13 | return User(id: "id1", email: "user1@email.com", name: "User 1") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Mocks/Cores/Analytics/MockAnalyticsProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockAnalyticsProvider.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Umbrella 10 | 11 | @testable import DeliTodo 12 | 13 | final class MockAnalyticsProvider: RuntimeProviderType { 14 | 15 | // MARK: - Expect 16 | 17 | var eventNameList: [String] = [] 18 | var parameterList: [[String: Any]?] = [] 19 | 20 | // MARK: - Protocol 21 | 22 | var className: String = "MockAnalyticsProvider" 23 | var selectorName: String = "" 24 | 25 | func log(_ eventName: String, parameters: [String : Any]?) { 26 | eventNameList.append(eventName) 27 | parameterList.append(parameters) 28 | } 29 | 30 | init() {} 31 | } 32 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Mocks/Errors/MockError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockError.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 08/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum MockError: Error { 12 | case notImplementated 13 | } 14 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Mocks/Repositories/MockTodoRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockTodoRepository.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 08/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | @testable import DeliTodo 12 | 13 | final class MockTodoRepository: TodoRepository { 14 | 15 | // MARK: - Expect 16 | 17 | var expectedGet: Single = .error(MockError.notImplementated) 18 | var expectedGets: Single<[Todo]> = .error(MockError.notImplementated) 19 | var expectedObserve: Observable = .error(MockError.notImplementated) 20 | var expectedObserves: Observable<[Todo]> = .error(MockError.notImplementated) 21 | var expectedInsert: Single = .error(MockError.notImplementated) 22 | var expectedUpdate: Single = .error(MockError.notImplementated) 23 | var expectedDelete: Single = .error(MockError.notImplementated) 24 | var expectedClear: Single = .error(MockError.notImplementated) 25 | 26 | var isGetCalled: Bool = false 27 | var isGetsCalled: Bool = false 28 | var isObserveCalled: Bool = false 29 | var isObservesCalled: Bool = false 30 | var isInsertCalled: Bool = false 31 | var isUpdateCalled: Bool = false 32 | var isDeleteCalled: Bool = false 33 | var isClearCalled: Bool = false 34 | 35 | var numberOfGetCalled: Int = 0 36 | var numberOfGetsCalled: Int = 0 37 | var numberOfObserveCalled: Int = 0 38 | var numberOfObservesCalled: Int = 0 39 | var numberOfInsertCalled: Int = 0 40 | var numberOfUpdateCalled: Int = 0 41 | var numberOfDeleteCalled: Int = 0 42 | var numberOfClearCalled: Int = 0 43 | 44 | // MARK: - Protocol 45 | 46 | func get(id: String, for user: User) -> Single { 47 | isGetCalled = true 48 | numberOfGetCalled += 1 49 | return expectedGet 50 | } 51 | func gets(for user: User) -> Single<[Todo]> { 52 | isGetsCalled = true 53 | numberOfGetsCalled += 1 54 | return expectedGets 55 | } 56 | 57 | func observe(id: String, for user: User) -> Observable { 58 | isObserveCalled = true 59 | numberOfObserveCalled += 1 60 | return expectedObserve 61 | } 62 | func observes(for user: User) -> Observable<[Todo]> { 63 | isObservesCalled = true 64 | numberOfObservesCalled += 1 65 | return expectedObserves 66 | } 67 | 68 | func insert(_ todo: Todo, for user: User) -> Single { 69 | isInsertCalled = true 70 | numberOfInsertCalled += 1 71 | return expectedInsert 72 | } 73 | func update(_ todo: Todo, for user: User) -> Single { 74 | isUpdateCalled = true 75 | numberOfUpdateCalled += 1 76 | return expectedUpdate 77 | } 78 | func delete(id: String, for user: User) -> Single { 79 | isDeleteCalled = true 80 | numberOfDeleteCalled += 1 81 | return expectedDelete 82 | } 83 | func clear(for user: User) -> Single { 84 | isClearCalled = true 85 | numberOfClearCalled += 1 86 | return expectedClear 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Mocks/Services/MockAuthService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockAuthService.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 08/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | @testable import DeliTodo 12 | 13 | final class MockAuthService: AuthService { 14 | 15 | // MARK: - Expect 16 | 17 | var expectedObserveUser: BehaviorSubject = BehaviorSubject(value: nil) 18 | var expectedCurrentUser: User? = nil 19 | var expectedSignUp: Single = .error(MockError.notImplementated) 20 | var expectedLogin: Single = .error(MockError.notImplementated) 21 | var expectedLogout: Single = .error(MockError.notImplementated) 22 | 23 | var isSignUpCalled: Bool = false 24 | var isLoginCalled: Bool = false 25 | var isLogoutCalled: Bool = false 26 | 27 | var numberOfSignUpCalled: Int = 0 28 | var numberOfLoginCalled: Int = 0 29 | var numberOfLogoutCalled: Int = 0 30 | 31 | // MARK: - Protocol 32 | 33 | func observeUser() -> Observable { 34 | return expectedObserveUser 35 | } 36 | func currentUser() -> User? { 37 | return expectedCurrentUser 38 | } 39 | 40 | func signUp(credential: Credential, user: User) -> Single { 41 | isSignUpCalled = true 42 | numberOfSignUpCalled += 1 43 | return expectedSignUp 44 | } 45 | func login(credential: Credential) -> Single { 46 | isLoginCalled = true 47 | numberOfLoginCalled += 1 48 | return expectedLogin 49 | } 50 | func logout() -> Single { 51 | isLogoutCalled = true 52 | numberOfLogoutCalled += 1 53 | return expectedLogout 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Mocks/Services/MockToastService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockToastService.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxCocoa 10 | import RxSwift 11 | 12 | @testable import DeliTodo 13 | 14 | final class MockToastService: ToastService { 15 | 16 | // MARK: - Expect 17 | 18 | var expectedShow: UUID = UUID() 19 | var isShowCalled: Bool = false 20 | var numberOfShowCalled: Int = 0 21 | 22 | // MARK: - Protocol 23 | 24 | let event = PublishRelay() 25 | 26 | @discardableResult 27 | func show(_ message: String, level: Toast.Level) -> UUID { 28 | isShowCalled = true 29 | numberOfShowCalled += 1 30 | return expectedShow 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Mocks/Services/MockTodoService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockTodoService.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | @testable import DeliTodo 12 | 13 | final class MockTodoService: TodoService { 14 | 15 | // MARK: - Expect 16 | 17 | var expectedGet: Single = .error(MockError.notImplementated) 18 | var expectedGets: Single<[Todo]> = .error(MockError.notImplementated) 19 | var expectedObserve: Observable = .error(MockError.notImplementated) 20 | var expectedObserves: Observable<[Todo]> = .error(MockError.notImplementated) 21 | var expectedInsert: Single = .error(MockError.notImplementated) 22 | var expectedUpdate: Single = .error(MockError.notImplementated) 23 | var expectedDelete: Single = .error(MockError.notImplementated) 24 | 25 | var isGetCalled: Bool = false 26 | var isGetsCalled: Bool = false 27 | var isObserveCalled: Bool = false 28 | var isObservesCalled: Bool = false 29 | var isInsertCalled: Bool = false 30 | var isUpdateCalled: Bool = false 31 | var isDeleteCalled: Bool = false 32 | 33 | var numberOfGetCalled: Int = 0 34 | var numberOfGetsCalled: Int = 0 35 | var numberOfObserveCalled: Int = 0 36 | var numberOfObservesCalled: Int = 0 37 | var numberOfInsertCalled: Int = 0 38 | var numberOfUpdateCalled: Int = 0 39 | var numberOfDeleteCalled: Int = 0 40 | 41 | // MARK: - Protocol 42 | 43 | func get(id: String) -> Single { 44 | isGetCalled = true 45 | numberOfGetCalled += 1 46 | return expectedGet 47 | } 48 | func gets() -> Single<[Todo]> { 49 | isGetsCalled = true 50 | numberOfGetsCalled += 1 51 | return expectedGets 52 | } 53 | 54 | func observe(id: String) -> Observable { 55 | isObserveCalled = true 56 | numberOfObserveCalled += 1 57 | return expectedObserve 58 | } 59 | func observes() -> Observable<[Todo]> { 60 | isObservesCalled = true 61 | numberOfObservesCalled += 1 62 | return expectedObserves 63 | } 64 | 65 | func insert(title: String) -> Single { 66 | isInsertCalled = true 67 | numberOfInsertCalled += 1 68 | return expectedInsert 69 | } 70 | func update(todo: Todo) -> Single { 71 | isUpdateCalled = true 72 | numberOfUpdateCalled += 1 73 | return expectedUpdate 74 | } 75 | func delete(id: String) -> Single { 76 | isDeleteCalled = true 77 | numberOfDeleteCalled += 1 78 | return expectedDelete 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Reactors/AddTodoViewReactorSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTodoViewReactorSpec.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | import Deli 13 | import RxSwift 14 | 15 | @testable import DeliTodo 16 | 17 | final class AddTodoViewReactorSpec: QuickSpec { 18 | override func spec() { 19 | super.spec() 20 | 21 | var mockToastService: MockToastService! 22 | var mockTodoService: MockTodoService! 23 | var sut: AddTodoViewReactor! 24 | 25 | beforeEach { 26 | testModule.register( 27 | MockTodoService.self, 28 | resolver: { MockTodoService() }, 29 | qualifier: "", 30 | scope: .singleton 31 | ).link(TodoService.self) 32 | 33 | testModule.register( 34 | MockToastService.self, 35 | resolver: { MockToastService() }, 36 | qualifier: "", 37 | scope: .singleton 38 | ).link(ToastService.self) 39 | 40 | mockToastService = AppContext.shared.get(MockToastService.self) 41 | mockTodoService = AppContext.shared.get(MockTodoService.self) 42 | sut = AppContext.shared.get(AddTodoViewReactor.self) 43 | } 44 | describe("AddTodoViewReactor's") { 45 | context("when set allow title") { 46 | beforeEach { 47 | sut.action.onNext(.setTitle("Title")) 48 | } 49 | it("state.title should equal to 'Title'") { 50 | expect(sut.currentState.title).toEventually(equal("Title")) 51 | } 52 | context("after called Action.create") { 53 | context("success test") { 54 | beforeEach { 55 | mockTodoService.expectedInsert = .just("Title") 56 | sut.action.onNext(.create) 57 | } 58 | it("state.isCreated should be true") { 59 | expect(sut.currentState.isCreated).toEventually(beTrue()) 60 | } 61 | } 62 | context("failed test") { 63 | beforeEach { 64 | mockTodoService.expectedInsert = .error(TodoRepositoryError.notFound) 65 | sut.action.onNext(.create) 66 | } 67 | it("should showed toast") { 68 | expect(mockToastService.isShowCalled).toEventually(beTrue()) 69 | } 70 | } 71 | } 72 | } 73 | context("when set denied title") { 74 | beforeEach { 75 | sut.action.onNext(.setTitle("")) 76 | } 77 | context("after called Action.create") { 78 | beforeEach { 79 | sut.action.onNext(.create) 80 | } 81 | it("should showed toast") { 82 | expect(mockToastService.isShowCalled).toEventually(beTrue()) 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Reactors/MainNavigationViewReactorSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainNavigationViewReactorSpec.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | import Deli 13 | import RxSwift 14 | 15 | @testable import DeliTodo 16 | 17 | final class MainNavigationViewReactorSpec: QuickSpec { 18 | override func spec() { 19 | super.spec() 20 | 21 | var sut: MainNavigationViewReactor! 22 | 23 | beforeEach { 24 | sut = AppContext.shared.get(MainNavigationViewReactor.self) 25 | } 26 | describe("MainNavigationViewReactor's") { 27 | context("when change tab 'more'") { 28 | beforeEach { 29 | sut.action.onNext(.changeTab(.more)) 30 | } 31 | it("state.currentTab should equal to 'more'") { 32 | expect(sut.currentState.currentTab).toEventually(equal(.more)) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Reactors/SplashViewReactorSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashViewReactorSpec.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | import Deli 13 | import RxSwift 14 | 15 | @testable import DeliTodo 16 | 17 | final class SplashViewReactorSpec: QuickSpec { 18 | override func spec() { 19 | super.spec() 20 | 21 | var mockAuthService: MockAuthService! 22 | var sut: SplashViewReactor! 23 | 24 | beforeEach { 25 | testModule.register( 26 | MockAuthService.self, 27 | resolver: { MockAuthService() }, 28 | qualifier: "", 29 | scope: .singleton 30 | ).link(AuthService.self) 31 | 32 | mockAuthService = AppContext.shared.get(MockAuthService.self) 33 | sut = AppContext.shared.get(SplashViewReactor.self) 34 | } 35 | describe("SplashViewReactor's") { 36 | context("when called Action.checkAuthenticated") { 37 | context("with logged-in") { 38 | beforeEach { 39 | mockAuthService.expectedCurrentUser = DummyUser.user 40 | sut.action.onNext(.checkAuthenticated) 41 | } 42 | it("state.isAuthenticated should be true") { 43 | expect(sut.currentState.isAuthenticated).toEventually(beTrue()) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Services/ToastServiceSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToastServiceSpec.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | import Deli 13 | import RxSwift 14 | 15 | @testable import DeliTodo 16 | 17 | final class ToastServiceSpec: QuickSpec { 18 | override func spec() { 19 | super.spec() 20 | 21 | var sut: ToastService! 22 | var disposeBag: DisposeBag! 23 | 24 | beforeEach { 25 | sut = AppContext.shared.get(ToastService.self) 26 | 27 | disposeBag = DisposeBag() 28 | } 29 | describe("ToastService's") { 30 | var toastEvent = [Toast]() 31 | 32 | beforeEach { 33 | toastEvent = [] 34 | 35 | sut.event 36 | .subscribe(onNext: { toastEvent.append($0) }) 37 | .disposed(by: disposeBag) 38 | } 39 | context("when called show()") { 40 | beforeEach { 41 | sut.show("Test Message", level: .normal) 42 | } 43 | it("should received toast") { 44 | expect(toastEvent.first?.message).toEventually(equal("Test Message")) 45 | expect(toastEvent.first?.level).toEventually(equal(.normal)) 46 | expect(toastEvent.first?.duration).toEventually(equal(3)) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/Services/TodoSyncServiceSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoSyncServiceSpec.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Quick 10 | import Nimble 11 | 12 | import Deli 13 | import RxBlocking 14 | import RxSwift 15 | 16 | @testable import DeliTodo 17 | 18 | final class TodoSyncServiceSpec: QuickSpec { 19 | override func spec() { 20 | super.spec() 21 | 22 | var sut: SyncService! 23 | var mockLocalTodoRepository: MockTodoRepository! 24 | var mockRemoteTodoRepository: MockTodoRepository! 25 | var mockAuthService: MockAuthService! 26 | 27 | beforeEach { 28 | testModule.register( 29 | MockTodoRepository.self, 30 | resolver: { MockTodoRepository() }, 31 | qualifier: "local", 32 | scope: .singleton 33 | ).link(TodoRepository.self) 34 | 35 | testModule.register( 36 | MockTodoRepository.self, 37 | resolver: { MockTodoRepository() }, 38 | qualifier: "remote", 39 | scope: .singleton 40 | ).link(TodoRepository.self) 41 | 42 | testModule.register( 43 | MockAuthService.self, 44 | resolver: { MockAuthService() }, 45 | qualifier: "", 46 | scope: .singleton 47 | ).link(AuthService.self) 48 | 49 | mockLocalTodoRepository = AppContext.shared.get(MockTodoRepository.self, qualifier: "local") 50 | mockRemoteTodoRepository = AppContext.shared.get(MockTodoRepository.self, qualifier: "remote") 51 | mockAuthService = AppContext.shared.get(MockAuthService.self) 52 | sut = AppContext.shared.get(TodoSyncServiceImpl.self, qualifier: "todo") 53 | } 54 | describe("SyncService's") { 55 | beforeEach { 56 | mockLocalTodoRepository.expectedClear = .just(Void()) 57 | mockRemoteTodoRepository.expectedClear = .just(Void()) 58 | 59 | mockLocalTodoRepository.expectedInsert = .just("") 60 | mockRemoteTodoRepository.expectedInsert = .just("") 61 | 62 | mockLocalTodoRepository.expectedGets = .just([DummyTodo.checked]) 63 | mockRemoteTodoRepository.expectedGets = .just([DummyTodo.nonChecked]) 64 | mockLocalTodoRepository.expectedObserves = .just([DummyTodo.checked]) 65 | mockRemoteTodoRepository.expectedObserves = .just([DummyTodo.nonChecked]) 66 | } 67 | context("when logged-in") { 68 | beforeEach { 69 | mockAuthService.expectedCurrentUser = DummyUser.user 70 | mockAuthService.expectedObserveUser.onNext(DummyUser.user) 71 | } 72 | it("should called local TodoRepository.clear()") { 73 | expect(mockLocalTodoRepository.numberOfClearCalled).toEventually(equal(1)) 74 | } 75 | it("should called local TodoRepository.insert()") { 76 | expect(mockLocalTodoRepository.numberOfInsertCalled).toEventually(equal(1)) 77 | } 78 | it("should called local and remote TodoRepository.observes()") { 79 | expect(mockLocalTodoRepository.numberOfObservesCalled).toEventually(equal(1)) 80 | expect(mockRemoteTodoRepository.numberOfObservesCalled).toEventually(equal(1)) 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /DeliTodoTests/Sources/TestMain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // DeliTodoTests 4 | // 5 | // Created by Kawoou on 09/10/2018. 6 | // Copyright © 2018 Kawoou. All rights reserved. 7 | // 8 | 9 | import Deli 10 | 11 | let testModule = ModuleFactory() 12 | -------------------------------------------------------------------------------- /DeliTodoTests/Supporting Files/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 | -------------------------------------------------------------------------------- /Design/Design.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kawoou/DeliTodo/ad06eb128cc167748921280a7998a643fe6f5545/Design/Design.sketch -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jungwon An 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 | platform :ios, '11.0' 2 | 3 | target 'DeliTodo' do 4 | use_frameworks! 5 | 6 | # Binary 7 | pod 'Deli', '= 0.6.0' 8 | pod 'SwiftLint', '= 0.27.0' 9 | pod 'SwiftGen', '= 6.0.1' 10 | pod 'LicensePlist', '= 1.8.8' 11 | 12 | # Crashlytics 13 | pod 'Fabric', '= 1.7.13' 14 | pod 'Crashlytics', '= 3.10.9' 15 | 16 | # Firebase 17 | pod 'FirebaseCore', '= 5.1.4' 18 | pod 'FirebaseAuth', '= 5.0.4' 19 | pod 'FirebaseAnalytics', '= 5.2.0' 20 | pod 'FirebaseDatabase', '= 5.0.3' 21 | 22 | # Analytics 23 | pod 'Umbrella', '= 0.7.1' 24 | pod 'Umbrella/Firebase', '= 0.7.1' 25 | 26 | target 'DeliTodoTests' do 27 | inherit! :search_paths 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Crashlytics (3.10.9): 3 | - Fabric (~> 1.7.13) 4 | - Deli (0.6.0): 5 | - DeliBinary (~> 0.6.0) 6 | - DeliBinary (0.6.0) 7 | - Fabric (1.7.13) 8 | - FirebaseAnalytics (5.2.0): 9 | - FirebaseCore (~> 5.1) 10 | - FirebaseInstanceID (~> 3.2) 11 | - GoogleAppMeasurement (~> 5.2) 12 | - GoogleUtilities/AppDelegateSwizzler (~> 5.2) 13 | - GoogleUtilities/MethodSwizzler (~> 5.2) 14 | - GoogleUtilities/Network (~> 5.2) 15 | - "GoogleUtilities/NSData+zlib (~> 5.2)" 16 | - nanopb (~> 0.3) 17 | - FirebaseAuth (5.0.4): 18 | - FirebaseAuthInterop (~> 1.0) 19 | - FirebaseCore (~> 5.0) 20 | - GoogleUtilities/Environment (~> 5.2) 21 | - GTMSessionFetcher/Core (~> 1.1) 22 | - FirebaseAuthInterop (1.0.0) 23 | - FirebaseCore (5.1.4): 24 | - GoogleUtilities/Logger (~> 5.2) 25 | - FirebaseDatabase (5.0.3): 26 | - FirebaseCore (~> 5.0) 27 | - leveldb-library (~> 1.18) 28 | - FirebaseInstanceID (3.2.2): 29 | - FirebaseCore (~> 5.1) 30 | - GoogleUtilities/Environment (~> 5.3) 31 | - GoogleUtilities/UserDefaults (~> 5.3) 32 | - GoogleAppMeasurement (5.2.0): 33 | - GoogleUtilities/AppDelegateSwizzler (~> 5.2) 34 | - GoogleUtilities/MethodSwizzler (~> 5.2) 35 | - GoogleUtilities/Network (~> 5.2) 36 | - "GoogleUtilities/NSData+zlib (~> 5.2)" 37 | - nanopb (~> 0.3) 38 | - GoogleUtilities/AppDelegateSwizzler (5.3.0): 39 | - GoogleUtilities/Environment 40 | - GoogleUtilities/Logger 41 | - GoogleUtilities/Network 42 | - GoogleUtilities/Environment (5.3.0) 43 | - GoogleUtilities/Logger (5.3.0): 44 | - GoogleUtilities/Environment 45 | - GoogleUtilities/MethodSwizzler (5.3.0): 46 | - GoogleUtilities/Logger 47 | - GoogleUtilities/Network (5.3.0): 48 | - GoogleUtilities/Logger 49 | - "GoogleUtilities/NSData+zlib" 50 | - GoogleUtilities/Reachability 51 | - "GoogleUtilities/NSData+zlib (5.3.0)" 52 | - GoogleUtilities/Reachability (5.3.0): 53 | - GoogleUtilities/Logger 54 | - GoogleUtilities/UserDefaults (5.3.0): 55 | - GoogleUtilities/Logger 56 | - GTMSessionFetcher/Core (1.2.0) 57 | - leveldb-library (1.20) 58 | - LicensePlist (1.8.8) 59 | - nanopb (0.3.8): 60 | - nanopb/decode (= 0.3.8) 61 | - nanopb/encode (= 0.3.8) 62 | - nanopb/decode (0.3.8) 63 | - nanopb/encode (0.3.8) 64 | - SwiftGen (6.0.1) 65 | - SwiftLint (0.27.0) 66 | - Umbrella (0.7.1): 67 | - Umbrella/Core (= 0.7.1) 68 | - Umbrella/Core (0.7.1) 69 | - Umbrella/Firebase (0.7.1): 70 | - Umbrella/Core 71 | 72 | DEPENDENCIES: 73 | - Crashlytics (= 3.10.9) 74 | - Deli (= 0.6.0) 75 | - Fabric (= 1.7.13) 76 | - FirebaseAnalytics (= 5.2.0) 77 | - FirebaseAuth (= 5.0.4) 78 | - FirebaseCore (= 5.1.4) 79 | - FirebaseDatabase (= 5.0.3) 80 | - LicensePlist (= 1.8.8) 81 | - SwiftGen (= 6.0.1) 82 | - SwiftLint (= 0.27.0) 83 | - Umbrella (= 0.7.1) 84 | - Umbrella/Firebase (= 0.7.1) 85 | 86 | SPEC REPOS: 87 | https://github.com/cocoapods/specs.git: 88 | - Crashlytics 89 | - Deli 90 | - DeliBinary 91 | - Fabric 92 | - FirebaseAnalytics 93 | - FirebaseAuth 94 | - FirebaseAuthInterop 95 | - FirebaseCore 96 | - FirebaseDatabase 97 | - FirebaseInstanceID 98 | - GoogleAppMeasurement 99 | - GoogleUtilities 100 | - GTMSessionFetcher 101 | - leveldb-library 102 | - LicensePlist 103 | - nanopb 104 | - SwiftGen 105 | - SwiftLint 106 | - Umbrella 107 | 108 | SPEC CHECKSUMS: 109 | Crashlytics: 55e24fc23989680285a21cb1146578d9d18e432c 110 | Deli: a1ed277f09a03ae5670796277a227c31c325b6b9 111 | DeliBinary: 7bf249cffd8e2707a54135c76bf818a4924c670f 112 | Fabric: 25d0963b691fc97be566392243ff0ecef5a68338 113 | FirebaseAnalytics: 831f1f127f4a75698e9875a87bf7e2668730d953 114 | FirebaseAuth: 504b198ceb3472dca5c65bb95544ea44cfc9439e 115 | FirebaseAuthInterop: 0ffa57668be100582bb7643d4fcb7615496c41fc 116 | FirebaseCore: 2a84b6b325792a4319ef71ee18819dcba08d2fd7 117 | FirebaseDatabase: e2bcbc106adc4b11a2da3ec2eb63c0c4a44f2f54 118 | FirebaseInstanceID: 78ba376fcd5b94c001f9999b2cbd3d1f1e56e78d 119 | GoogleAppMeasurement: 2b3a023a61239c8d002e6e4fcf4abce8eddce0e0 120 | GoogleUtilities: 760ccb53b7c7f40f9c02d8c241f76f841a7a6162 121 | GTMSessionFetcher: 0c4baf0a73acd0041bf9f71ea018deedab5ea84e 122 | leveldb-library: 08cba283675b7ed2d99629a4bc5fd052cd2bb6a5 123 | LicensePlist: c30d20e39985de8600cccda21096cfa98865483b 124 | nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 125 | SwiftGen: 6b3ee807efa0231541a3777c9ed5c5d3130dfdfb 126 | SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073 127 | Umbrella: 90c8dea533add4a072dfd48fbc3a53f62c6e0716 128 | 129 | PODFILE CHECKSUM: 358e36c4c56e31f25cc30e730aab9bcc83f581c0 130 | 131 | COCOAPODS: 1.6.0.beta.1 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DeliTodo 2 | ======= 3 | ![Swift](https://img.shields.io/badge/Swift-4.2-orange.svg) 4 | [![CI Status](https://travis-ci.org/kawoou/DeliTodo.svg?branch=master)](https://travis-ci.org/kawoou/DeliTodo) 5 | [![Codecov](https://img.shields.io/codecov/c/github/kawoou/DeliTodo.svg)](https://codecov.io/gh/kawoou/DeliTodo) 6 | 7 | Todo application for iOS using [Deli](https://github.com/kawoou/Deli). 8 | 9 | Screenshot 10 | 11 |
12 | 13 | ## Usage 14 | 15 | ```bash 16 | pod install --repo-update 17 | carthage update --cache-builds --platform iOS 18 | open DeliTodo.xcworkspace/ 19 | ``` 20 | 21 |
22 | 23 | ## Requirements 24 | * iOS 11+ 25 | * Swift 4.2 26 | * [CocoaPods](https://github.com/CocoaPods/CocoaPods) 27 | * [Carthage](https://github.com/Carthage/Carthage) 28 | 29 |
30 | 31 | ## Dependencies 32 | 33 | **DI** 34 | 35 | * [Deli](https://github.com/kawoou/Deli) 36 | 37 | **Architecture** 38 | 39 | * [ReactorKit](https://github.com/ReactorKit/ReactorKit) 40 | 41 | **Supporting** 42 | 43 | * [SwiftLint](https://github.com/realm/SwiftLint) 44 | * [SwiftGen](https://github.com/SwiftGen/SwiftGen) 45 | * [LicensePlist](https://github.com/mono0926/LicensePlist) 46 | 47 | **Crash** 48 | 49 | * [Crashlytics](https://firebase.google.com/docs/crashlytics) 50 | 51 | **[Firebase](https://firebase.google.com/)** 52 | 53 | * Auth 54 | * Analytics 55 | * Database 56 | 57 | **Analytics** 58 | 59 | * [Umbrella](https://github.com/devxoul/Umbrella) 60 | 61 | **Rx** 62 | 63 | * [RxSwift](https://github.com/ReactiveX/RxSwift) 64 | * [RxOptional](https://github.com/RxSwiftCommunity/RxOptional) 65 | * [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) 66 | * [RxValidator](https://github.com/vbmania/RxValidator) 67 | 68 | **Database** 69 | 70 | * [Realm](https://github.com/Realm/realm-cocoa) 71 | 72 | **Logging** 73 | 74 | * [SwiftyBeaver](https://github.com/SwiftyBeaver/SwiftyBeaver) 75 | 76 | **Deeplink** 77 | 78 | * [DeepLinkKit](https://github.com/button/DeepLinkKit) 79 | 80 | **UI** 81 | 82 | * [SnapKit](https://github.com/SnapKit/SnapKit) 83 | * [Gradients](https://github.com/cruisediary/Gradients) 84 | * [TextFieldEffects](https://github.com/raulriera/TextFieldEffects) 85 | * [SwipeCellKit](https://github.com/SwipeCellKit/SwipeCellKit) 86 | * [NotificationBanner](https://github.com/Daltron/NotificationBanner) 87 | 88 | **Test** 89 | 90 | * [Quick](https://github.com/Quick/Quick) 91 | * [Nimble](https://github.com/Quick/Nimble) 92 | 93 |
94 | 95 | ## Contributing 96 | 97 | Any discussions and pull requests are welcomed. 98 | 99 | If you want to contribute, [submit a pull request](https://github.com/kawoou/DeliTodo/compare). 100 | 101 |
102 | 103 | ## License 104 | 105 | DeliTodo is under MIT license. See the [LICENSE](https://github.com/kawoou/DeliTodo/blob/master/LICENSE) file for more info. 106 | 107 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "DeliTodoTests/" 3 | 4 | coverage: 5 | status: 6 | project: no 7 | patch: no 8 | changes: no 9 | -------------------------------------------------------------------------------- /deli.yml: -------------------------------------------------------------------------------- 1 | target: 2 | - DeliTodo 3 | 4 | config: 5 | DeliTodo: 6 | project: DeliTodo 7 | scheme: DeliTodo 8 | className: DeliFactory 9 | output: DeliTodo/Sources/DeliFactory.swift 10 | -------------------------------------------------------------------------------- /license_plist.yml: -------------------------------------------------------------------------------- 1 | github: 2 | - owner: kawoou 3 | name: Deli 4 | version: 0.5.0 5 | - owner: Realm 6 | name: SwiftLint 7 | version: 0.27.0 8 | - owner: SwiftGen 9 | name: SwiftGen 10 | version: 6.0.1 11 | - owner: ReactorKit 12 | name: ReactorKit 13 | verison: 1.2.0 14 | - owner: kawoou 15 | name: ReactorKit-Carthage 16 | version: 1.2.0 17 | - owner: devxoul 18 | name: Umbrella 19 | version: 0.7.1 20 | -------------------------------------------------------------------------------- /swiftgen.yml: -------------------------------------------------------------------------------- 1 | xcassets: 2 | inputs: DeliTodo/Resources/Assets.xcassets 3 | outputs: 4 | templateName: swift4 5 | output: DeliTodo/Sources/Assets.swift --------------------------------------------------------------------------------