├── .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 | 
4 | [](https://travis-ci.org/kawoou/DeliTodo)
5 | [](https://codecov.io/gh/kawoou/DeliTodo)
6 |
7 | Todo application for iOS using [Deli](https://github.com/kawoou/Deli).
8 |
9 |
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
--------------------------------------------------------------------------------