From f960634ffef89eaa11d7dea16e808142b7b74c12 Mon Sep 17 00:00:00 2001 From: Pierre Brisorgueil Date: Tue, 7 May 2019 11:39:22 +0200 Subject: [PATCH] feat(global): init local CRUD --- Cartfile | 4 +- Cartfile.resolved | 6 +- waosSwift.xcodeproj/project.pbxproj | 202 +++++++++++------- waosSwift/config/headers/BridgingHeader.h | 2 + waosSwift/config/localizations/Strings.swift | 6 +- .../en.lproj/Localizable.strings | 4 +- .../fr.lproj/Localizable.strings | 2 +- waosSwift/lib/helpers/Array.swift | 21 ++ .../lib/helpers/ReusableKit/ReusableKit.swift | 66 ++++++ .../UICollectionView+ReusableKit.swift | 72 +++++++ .../ReusableKit/UITableView+ReusableKit.swift | 44 ++++ .../lib/helpers/Rx/Array+SectionModel.swift | 40 ++++ .../lib/helpers/Rx/NSViewController+Rx.swift | 40 ++++ .../Rx/UICollectionView+RxReusableKit.swift | 21 ++ .../Rx/UITableView+RxReusableKit.swift | 21 ++ .../lib/helpers/Rx/UIViewController+Rx.swift | 68 ++++++ waosSwift/lib/helpers/Then.swift | 85 ++++++++ waosSwift/lib/services/SyncState.swift | 4 + .../Controllers/TaskCellController.swift | 48 +++++ .../Tasks/Controllers/TaskController.swift | 126 +++++++++++ .../Controllers/TaskEditController.swift | 47 ---- .../Controllers/TasksListController.swift | 110 ++++++---- waosSwift/modules/Tasks/Flows/TasksFlow.swift | 16 +- .../Tasks/Reactors/TaskCellReactor.swift | 28 +++ .../Tasks/Reactors/TaskEditReactor.swift | 49 ----- .../modules/Tasks/Reactors/TaskReactor.swift | 108 ++++++++++ .../Tasks/Reactors/TasksListReactor.swift | 101 ++++++--- .../modules/Tasks/Services/TaskService.swift | 68 ++++++ .../modules/Tasks/Services/TaskServices.swift | 24 --- .../Storyboards/TaskEditController.storyboard | 29 --- .../TasksListController.storyboard | 44 ---- waosSwift/modules/app/AppDelegate.swift | 2 + .../modules/app/AppServicesProvider.swift | 7 + ...iewCell.swift => CoreCellController.swift} | 10 +- waosSwift/modules/core/CoreController.swift | 46 ++++ waosSwift/modules/core/CoreFlow.swift | 18 +- waosSwift/modules/core/CoreService.swift | 15 ++ waosSwift/modules/core/CoreSteps.swift | 4 +- .../modules/core/CoreViewController.swift | 13 -- .../Controllers/OnBoardingController.swift | 181 ++++++++++++++++ .../OnBoardingIntroViewController.swift | 62 ------ .../onBoarding/Flows/OnBoardingFlow.swift | 11 +- ...wReactor.swift => OnBoardingReactor.swift} | 26 ++- .../OnboardingIntroViewController.storyboard | 66 ------ .../Controllers/SecondController.swift | 73 +++++++ .../Controllers/SecondViewController.swift | 43 ---- ...{SecondViewFlow.swift => SecondFlow.swift} | 14 +- ...dViewReactor.swift => SecondReactor.swift} | 10 +- .../SecondViewController.storyboard | 45 ---- 49 files changed, 1517 insertions(+), 635 deletions(-) create mode 100644 waosSwift/lib/helpers/Array.swift create mode 100644 waosSwift/lib/helpers/ReusableKit/ReusableKit.swift create mode 100644 waosSwift/lib/helpers/ReusableKit/UICollectionView+ReusableKit.swift create mode 100644 waosSwift/lib/helpers/ReusableKit/UITableView+ReusableKit.swift create mode 100644 waosSwift/lib/helpers/Rx/Array+SectionModel.swift create mode 100644 waosSwift/lib/helpers/Rx/NSViewController+Rx.swift create mode 100644 waosSwift/lib/helpers/Rx/UICollectionView+RxReusableKit.swift create mode 100644 waosSwift/lib/helpers/Rx/UITableView+RxReusableKit.swift create mode 100644 waosSwift/lib/helpers/Rx/UIViewController+Rx.swift create mode 100644 waosSwift/lib/helpers/Then.swift create mode 100644 waosSwift/lib/services/SyncState.swift create mode 100644 waosSwift/modules/Tasks/Controllers/TaskCellController.swift create mode 100644 waosSwift/modules/Tasks/Controllers/TaskController.swift delete mode 100644 waosSwift/modules/Tasks/Controllers/TaskEditController.swift create mode 100644 waosSwift/modules/Tasks/Reactors/TaskCellReactor.swift delete mode 100644 waosSwift/modules/Tasks/Reactors/TaskEditReactor.swift create mode 100644 waosSwift/modules/Tasks/Reactors/TaskReactor.swift create mode 100644 waosSwift/modules/Tasks/Services/TaskService.swift delete mode 100644 waosSwift/modules/Tasks/Services/TaskServices.swift delete mode 100644 waosSwift/modules/Tasks/Storyboards/TaskEditController.storyboard delete mode 100644 waosSwift/modules/Tasks/Storyboards/TasksListController.storyboard create mode 100644 waosSwift/modules/app/AppServicesProvider.swift rename waosSwift/modules/core/{CoreTableViewCell.swift => CoreCellController.swift} (82%) create mode 100644 waosSwift/modules/core/CoreController.swift create mode 100644 waosSwift/modules/core/CoreService.swift delete mode 100644 waosSwift/modules/core/CoreViewController.swift create mode 100755 waosSwift/modules/onBoarding/Controllers/OnBoardingController.swift delete mode 100755 waosSwift/modules/onBoarding/Controllers/OnBoardingIntroViewController.swift rename waosSwift/modules/onBoarding/Reactors/{OnBoardingIntroViewReactor.swift => OnBoardingReactor.swift} (50%) delete mode 100755 waosSwift/modules/onBoarding/Storyboards/OnboardingIntroViewController.storyboard create mode 100644 waosSwift/modules/secondController/Controllers/SecondController.swift delete mode 100644 waosSwift/modules/secondController/Controllers/SecondViewController.swift rename waosSwift/modules/secondController/Flows/{SecondViewFlow.swift => SecondFlow.swift} (75%) rename waosSwift/modules/secondController/Reactors/{SecondViewReactor.swift => SecondReactor.swift} (87%) delete mode 100644 waosSwift/modules/secondController/Storyboards/SecondViewController.storyboard diff --git a/Cartfile b/Cartfile index 7f6d0f1..3c80ef2 100644 --- a/Cartfile +++ b/Cartfile @@ -1,7 +1,9 @@ github "ReactiveX/RxSwift" +github "RxSwiftCommunity/RxOptional" github "RxSwiftCommunity/RxFlow" github "ReactorKit/ReactorKit" github "CocoaLumberjack/CocoaLumberjack" github "AliSoftware/Reusable" github "SwiftyJSON/SwiftyJSON" -github "RxSwiftCommunity/RxDataSources" \ No newline at end of file +github "RxSwiftCommunity/RxDataSources" +github "SnapKit/SnapKit" \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved index 14494d4..d04f15e 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,7 +1,9 @@ github "AliSoftware/Reusable" "4.0.5" -github "CocoaLumberjack/CocoaLumberjack" "3.5.2" +github "CocoaLumberjack/CocoaLumberjack" "3.5.3" github "ReactiveX/RxSwift" "4.5.0" github "ReactorKit/ReactorKit" "1.2.1" github "RxSwiftCommunity/RxDataSources" "3.1.0" -github "RxSwiftCommunity/RxFlow" "2.1.0" +github "RxSwiftCommunity/RxFlow" "2.2.0" +github "RxSwiftCommunity/RxOptional" "3.6.2" +github "SnapKit/SnapKit" "5.0.0" github "SwiftyJSON/SwiftyJSON" "5.0.0" diff --git a/waosSwift.xcodeproj/project.pbxproj b/waosSwift.xcodeproj/project.pbxproj index f73c284..e470a97 100644 --- a/waosSwift.xcodeproj/project.pbxproj +++ b/waosSwift.xcodeproj/project.pbxproj @@ -11,7 +11,7 @@ BF0CD6B622563CC200844F9A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0CD6B822563CC200844F9A /* Localizable.strings */; }; BF425484221EBB9700395AE6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF425483221EBB9700395AE6 /* AppDelegate.swift */; }; BF425486221EBB9700395AE6 /* TasksListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF425485221EBB9700395AE6 /* TasksListController.swift */; }; - BF425488221EBB9700395AE6 /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF425487221EBB9700395AE6 /* SecondViewController.swift */; }; + BF425488221EBB9700395AE6 /* SecondController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF425487221EBB9700395AE6 /* SecondController.swift */; }; BF42548D221EBB9800395AE6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF42548C221EBB9800395AE6 /* Assets.xcassets */; }; BF425490221EBB9800395AE6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF42548E221EBB9800395AE6 /* LaunchScreen.storyboard */; }; BF42549B221EBB9800395AE6 /* waosSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF42549A221EBB9800395AE6 /* waosSwiftTests.swift */; }; @@ -24,20 +24,18 @@ BF4A2F40225B53150001B4CE /* ReactorKitRuntime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4A2F3E225B53150001B4CE /* ReactorKitRuntime.framework */; }; BF4A2F45225B751F0001B4CE /* AppFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F44225B751F0001B4CE /* AppFlow.swift */; }; BF4A2F4D225B79A80001B4CE /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F4C225B79A80001B4CE /* Preferences.swift */; }; - BF4A2F6A225B7C200001B4CE /* OnBoardingIntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F5E225B7C200001B4CE /* OnBoardingIntroViewController.swift */; }; - BF4A2F6B225B7C200001B4CE /* OnBoardingIntroViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F5F225B7C200001B4CE /* OnBoardingIntroViewReactor.swift */; }; + BF4A2F6A225B7C200001B4CE /* OnBoardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F5E225B7C200001B4CE /* OnBoardingController.swift */; }; + BF4A2F6B225B7C200001B4CE /* OnBoardingReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F5F225B7C200001B4CE /* OnBoardingReactor.swift */; }; BF4A2F6D225B7D0B0001B4CE /* Reusable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4A2F6C225B7D0B0001B4CE /* Reusable.framework */; }; BF4A2F6F225BB2B10001B4CE /* OnBoardingFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F6E225BB2B10001B4CE /* OnBoardingFlow.swift */; }; BF4A2F71225BB2CD0001B4CE /* CoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F70225BB2CD0001B4CE /* CoreFlow.swift */; }; BF4A2F74225BB4B90001B4CE /* CoreSteps.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F73225BB4B90001B4CE /* CoreSteps.swift */; }; - BF4A2F76225BB6190001B4CE /* CoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F75225BB6190001B4CE /* CoreViewController.swift */; }; - BF4A2F7C225BB89C0001B4CE /* SecondViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF4A2F7B225BB89C0001B4CE /* SecondViewController.storyboard */; }; + BF4A2F76225BB6190001B4CE /* CoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F75225BB6190001B4CE /* CoreController.swift */; }; BF4A2F7E225BB9270001B4CE /* TasksListReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F7D225BB9270001B4CE /* TasksListReactor.swift */; }; - BF4A2F80225BB9360001B4CE /* SecondViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F7F225BB9360001B4CE /* SecondViewReactor.swift */; }; + BF4A2F80225BB9360001B4CE /* SecondReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F7F225BB9360001B4CE /* SecondReactor.swift */; }; BF4A2F82225BBA3F0001B4CE /* TasksFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F81225BBA3F0001B4CE /* TasksFlow.swift */; }; - BF4A2F84225BBA4D0001B4CE /* SecondViewFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F83225BBA4D0001B4CE /* SecondViewFlow.swift */; }; + BF4A2F84225BBA4D0001B4CE /* SecondFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F83225BBA4D0001B4CE /* SecondFlow.swift */; }; BF4A2F87225C8F160001B4CE /* UILocalizations.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2F86225C8F160001B4CE /* UILocalizations.swift */; }; - BF4A2F8A225C9C830001B4CE /* OnboardingIntroViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF4A2F5D225B7C200001B4CE /* OnboardingIntroViewController.storyboard */; }; BF4A2F9E225CA6DB0001B4CE /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = BF4A2F8D225CA6DB0001B4CE /* CHANGELOG.md */; }; BF4A2FA0225CA6DB0001B4CE /* KNOWLEDGES.md in Resources */ = {isa = PBXBuildFile; fileRef = BF4A2F8F225CA6DB0001B4CE /* KNOWLEDGES.md */; }; BF4A2FA9225CA6DB0001B4CE /* Readme.md in Resources */ = {isa = PBXBuildFile; fileRef = BF4A2F9A225CA6DB0001B4CE /* Readme.md */; }; @@ -53,13 +51,28 @@ BF4A2FD42265F4020001B4CE /* UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF4A2FD32265F4020001B4CE /* UserDefaults.swift */; }; BF4A2FD9226710F10001B4CE /* RxDataSources.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF4A2FD8226710F10001B4CE /* RxDataSources.framework */; }; BF794D3A22671CD8000B19F3 /* TasksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF794D3922671CD8000B19F3 /* TasksModel.swift */; }; - BF794D3E22672474000B19F3 /* CoreTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF794D3D22672474000B19F3 /* CoreTableViewCell.swift */; }; + BF794D3E22672474000B19F3 /* CoreCellController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF794D3D22672474000B19F3 /* CoreCellController.swift */; }; BF794D41226856E6000B19F3 /* Differentiator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF794D40226856E6000B19F3 /* Differentiator.framework */; }; - BF794D482268750C000B19F3 /* TasksListController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF4A2F79225BB6FE0001B4CE /* TasksListController.storyboard */; }; - BF794D51226880FB000B19F3 /* TaskServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF794D50226880FB000B19F3 /* TaskServices.swift */; }; - BF794D532269C4FB000B19F3 /* TaskEditController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF794D522269C4FB000B19F3 /* TaskEditController.swift */; }; - BF794D552269C505000B19F3 /* TaskEditReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF794D542269C505000B19F3 /* TaskEditReactor.swift */; }; - BF794D572269C51C000B19F3 /* TaskEditController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF794D562269C51C000B19F3 /* TaskEditController.storyboard */; }; + BF794D51226880FB000B19F3 /* TaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF794D50226880FB000B19F3 /* TaskService.swift */; }; + BF794D532269C4FB000B19F3 /* TaskController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF794D522269C4FB000B19F3 /* TaskController.swift */; }; + BF8C6721227C2CF00012B5A8 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C6720227C2CF00012B5A8 /* Array.swift */; }; + BF8C6723227C3E1E0012B5A8 /* TaskCellReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C6722227C3E1E0012B5A8 /* TaskCellReactor.swift */; }; + BF8C6725227C3E310012B5A8 /* TaskCellController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C6724227C3E310012B5A8 /* TaskCellController.swift */; }; + BF8C6727227C43E80012B5A8 /* Array+SectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C6726227C43E80012B5A8 /* Array+SectionModel.swift */; }; + BF8C672A2280176B0012B5A8 /* ReusableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C67292280176B0012B5A8 /* ReusableKit.swift */; }; + BF8C672C2280177E0012B5A8 /* UICollectionView+ReusableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C672B2280177E0012B5A8 /* UICollectionView+ReusableKit.swift */; }; + BF8C672E228017940012B5A8 /* UITableView+ReusableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C672D228017940012B5A8 /* UITableView+ReusableKit.swift */; }; + BF8C6731228022140012B5A8 /* UICollectionView+RxReusableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C6730228022140012B5A8 /* UICollectionView+RxReusableKit.swift */; }; + BF8C67332280222A0012B5A8 /* UITableView+RxReusableKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8C67322280222A0012B5A8 /* UITableView+RxReusableKit.swift */; }; + BF8F6ED1227063F100B9447A /* RxOptional.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8F6ED0227063F100B9447A /* RxOptional.framework */; }; + BF8F6ED92273442100B9447A /* SnapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF8F6ED82273442100B9447A /* SnapKit.framework */; }; + BF8F6EDB2273453100B9447A /* Then.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8F6EDA2273453100B9447A /* Then.swift */; }; + BFDE25FE227B052D008CBD30 /* NSViewController+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE25FD227B052D008CBD30 /* NSViewController+Rx.swift */; }; + BFDE2600227B053D008CBD30 /* UIViewController+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE25FF227B053D008CBD30 /* UIViewController+Rx.swift */; }; + BFDE2602227B17D4008CBD30 /* AppServicesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE2601227B17D4008CBD30 /* AppServicesProvider.swift */; }; + BFDE2604227B185E008CBD30 /* CoreService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFDE2603227B185E008CBD30 /* CoreService.swift */; }; + BFE4BB45226CAFB400739735 /* SyncState.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4BB44226CAFB400739735 /* SyncState.swift */; }; + BFE4BB47226CC7BD00739735 /* TaskReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFE4BB46226CC7BD00739735 /* TaskReactor.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -87,7 +100,7 @@ BF425480221EBB9700395AE6 /* waosSwift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = waosSwift.app; sourceTree = BUILT_PRODUCTS_DIR; }; BF425483221EBB9700395AE6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; BF425485221EBB9700395AE6 /* TasksListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksListController.swift; sourceTree = ""; }; - BF425487221EBB9700395AE6 /* SecondViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; + BF425487221EBB9700395AE6 /* SecondController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondController.swift; sourceTree = ""; }; BF42548C221EBB9800395AE6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; BF42548F221EBB9800395AE6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; BF425491221EBB9800395AE6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -108,20 +121,17 @@ BF4A2F41225B6AD70001B4CE /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; BF4A2F44225B751F0001B4CE /* AppFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFlow.swift; sourceTree = ""; }; BF4A2F4C225B79A80001B4CE /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; - BF4A2F5D225B7C200001B4CE /* OnboardingIntroViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = OnboardingIntroViewController.storyboard; sourceTree = ""; }; - BF4A2F5E225B7C200001B4CE /* OnBoardingIntroViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnBoardingIntroViewController.swift; sourceTree = ""; }; - BF4A2F5F225B7C200001B4CE /* OnBoardingIntroViewReactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnBoardingIntroViewReactor.swift; sourceTree = ""; }; + BF4A2F5E225B7C200001B4CE /* OnBoardingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnBoardingController.swift; sourceTree = ""; }; + BF4A2F5F225B7C200001B4CE /* OnBoardingReactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnBoardingReactor.swift; sourceTree = ""; }; BF4A2F6C225B7D0B0001B4CE /* Reusable.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reusable.framework; path = Carthage/Build/iOS/Reusable.framework; sourceTree = ""; }; BF4A2F6E225BB2B10001B4CE /* OnBoardingFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingFlow.swift; sourceTree = ""; }; BF4A2F70225BB2CD0001B4CE /* CoreFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreFlow.swift; sourceTree = ""; }; BF4A2F73225BB4B90001B4CE /* CoreSteps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSteps.swift; sourceTree = ""; }; - BF4A2F75225BB6190001B4CE /* CoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreViewController.swift; sourceTree = ""; }; - BF4A2F79225BB6FE0001B4CE /* TasksListController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TasksListController.storyboard; sourceTree = ""; }; - BF4A2F7B225BB89C0001B4CE /* SecondViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SecondViewController.storyboard; sourceTree = ""; }; + BF4A2F75225BB6190001B4CE /* CoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreController.swift; sourceTree = ""; }; BF4A2F7D225BB9270001B4CE /* TasksListReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksListReactor.swift; sourceTree = ""; }; - BF4A2F7F225BB9360001B4CE /* SecondViewReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewReactor.swift; sourceTree = ""; }; + BF4A2F7F225BB9360001B4CE /* SecondReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondReactor.swift; sourceTree = ""; }; BF4A2F81225BBA3F0001B4CE /* TasksFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksFlow.swift; sourceTree = ""; }; - BF4A2F83225BBA4D0001B4CE /* SecondViewFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondViewFlow.swift; sourceTree = ""; }; + BF4A2F83225BBA4D0001B4CE /* SecondFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondFlow.swift; sourceTree = ""; }; BF4A2F86225C8F160001B4CE /* UILocalizations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UILocalizations.swift; sourceTree = ""; }; BF4A2F8D225CA6DB0001B4CE /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; }; BF4A2F8F225CA6DB0001B4CE /* KNOWLEDGES.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = KNOWLEDGES.md; sourceTree = SOURCE_ROOT; }; @@ -138,12 +148,28 @@ BF4A2FD32265F4020001B4CE /* UserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaults.swift; sourceTree = ""; }; BF4A2FD8226710F10001B4CE /* RxDataSources.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxDataSources.framework; path = Carthage/Build/iOS/RxDataSources.framework; sourceTree = ""; }; BF794D3922671CD8000B19F3 /* TasksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksModel.swift; sourceTree = ""; }; - BF794D3D22672474000B19F3 /* CoreTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTableViewCell.swift; sourceTree = ""; }; + BF794D3D22672474000B19F3 /* CoreCellController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreCellController.swift; sourceTree = ""; }; BF794D40226856E6000B19F3 /* Differentiator.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Differentiator.framework; path = Carthage/Build/iOS/Differentiator.framework; sourceTree = ""; }; - BF794D50226880FB000B19F3 /* TaskServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskServices.swift; sourceTree = ""; }; - BF794D522269C4FB000B19F3 /* TaskEditController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskEditController.swift; sourceTree = ""; }; - BF794D542269C505000B19F3 /* TaskEditReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskEditReactor.swift; sourceTree = ""; }; - BF794D562269C51C000B19F3 /* TaskEditController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TaskEditController.storyboard; sourceTree = ""; }; + BF794D50226880FB000B19F3 /* TaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskService.swift; sourceTree = ""; }; + BF794D522269C4FB000B19F3 /* TaskController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskController.swift; sourceTree = ""; }; + BF8C6720227C2CF00012B5A8 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; }; + BF8C6722227C3E1E0012B5A8 /* TaskCellReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCellReactor.swift; sourceTree = ""; }; + BF8C6724227C3E310012B5A8 /* TaskCellController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCellController.swift; sourceTree = ""; }; + BF8C6726227C43E80012B5A8 /* Array+SectionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+SectionModel.swift"; sourceTree = ""; }; + BF8C67292280176B0012B5A8 /* ReusableKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableKit.swift; sourceTree = ""; }; + BF8C672B2280177E0012B5A8 /* UICollectionView+ReusableKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+ReusableKit.swift"; sourceTree = ""; }; + BF8C672D228017940012B5A8 /* UITableView+ReusableKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableKit.swift"; sourceTree = ""; }; + BF8C6730228022140012B5A8 /* UICollectionView+RxReusableKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+RxReusableKit.swift"; sourceTree = ""; }; + BF8C67322280222A0012B5A8 /* UITableView+RxReusableKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+RxReusableKit.swift"; sourceTree = ""; }; + BF8F6ED0227063F100B9447A /* RxOptional.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxOptional.framework; path = Carthage/Build/iOS/RxOptional.framework; sourceTree = ""; }; + BF8F6ED82273442100B9447A /* SnapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SnapKit.framework; path = Carthage/Build/iOS/SnapKit.framework; sourceTree = ""; }; + BF8F6EDA2273453100B9447A /* Then.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Then.swift; sourceTree = ""; }; + BFDE25FD227B052D008CBD30 /* NSViewController+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSViewController+Rx.swift"; sourceTree = ""; }; + BFDE25FF227B053D008CBD30 /* UIViewController+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Rx.swift"; sourceTree = ""; }; + BFDE2601227B17D4008CBD30 /* AppServicesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppServicesProvider.swift; sourceTree = ""; }; + BFDE2603227B185E008CBD30 /* CoreService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreService.swift; sourceTree = ""; }; + BFE4BB44226CAFB400739735 /* SyncState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncState.swift; sourceTree = ""; }; + BFE4BB46226CC7BD00739735 /* TaskReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskReactor.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -162,7 +188,9 @@ BF4A2FB3226330470001B4CE /* CocoaLumberjackSwift.framework in Frameworks */, BF4A2F6D225B7D0B0001B4CE /* Reusable.framework in Frameworks */, BF4A2FC42264A2350001B4CE /* SwiftyJSON.framework in Frameworks */, + BF8F6ED1227063F100B9447A /* RxOptional.framework in Frameworks */, BF4A2FD9226710F10001B4CE /* RxDataSources.framework in Frameworks */, + BF8F6ED92273442100B9447A /* SnapKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -261,6 +289,8 @@ BF4A2F30225B4F7F0001B4CE /* Frameworks */ = { isa = PBXGroup; children = ( + BF8F6ED82273442100B9447A /* SnapKit.framework */, + BF8F6ED0227063F100B9447A /* RxOptional.framework */, BF794D40226856E6000B19F3 /* Differentiator.framework */, BF4A2FD8226710F10001B4CE /* RxDataSources.framework */, BF4A2FC32264A2350001B4CE /* SwiftyJSON.framework */, @@ -292,8 +322,9 @@ isa = PBXGroup; children = ( BF4A2FAD2260F0040001B4CE /* Logger.swift */, - BF4A2FCF2265D6330001B4CE /* ServicesProvider.swift */, BF4A2F4C225B79A80001B4CE /* Preferences.swift */, + BF4A2FCF2265D6330001B4CE /* ServicesProvider.swift */, + BFE4BB44226CAFB400739735 /* SyncState.swift */, ); path = services; sourceTree = ""; @@ -315,8 +346,9 @@ children = ( BF4A2F73225BB4B90001B4CE /* CoreSteps.swift */, BF4A2F70225BB2CD0001B4CE /* CoreFlow.swift */, - BF4A2F75225BB6190001B4CE /* CoreViewController.swift */, - BF794D3D22672474000B19F3 /* CoreTableViewCell.swift */, + BF4A2F75225BB6190001B4CE /* CoreController.swift */, + BF794D3D22672474000B19F3 /* CoreCellController.swift */, + BFDE2603227B185E008CBD30 /* CoreService.swift */, ); path = core; sourceTree = ""; @@ -327,7 +359,6 @@ BF794D5D2269D08A000B19F3 /* Controllers */, BF794D5E2269D095000B19F3 /* Flows */, BF794D602269D0A3000B19F3 /* Reactors */, - BF794D5F2269D09C000B19F3 /* Storyboards */, ); path = onBoarding; sourceTree = ""; @@ -340,7 +371,6 @@ BF794D462268724D000B19F3 /* Models */, BF794D4422687238000B19F3 /* Reactors */, BF794D4F226880ED000B19F3 /* Services */, - BF794D4722687254000B19F3 /* Storyboards */, ); path = Tasks; sourceTree = ""; @@ -351,7 +381,6 @@ BF794D582269D028000B19F3 /* Controllers */, BF794D592269D030000B19F3 /* Flows */, BF794D5B2269D042000B19F3 /* Reactors */, - BF794D5A2269D03C000B19F3 /* Storyboards */, ); path = secondController; sourceTree = ""; @@ -359,8 +388,12 @@ BF4A2F85225C8F050001B4CE /* helpers */ = { isa = PBXGroup; children = ( + BF8C6728228016070012B5A8 /* ReusableKit */, + BFDE25FC227B0522008CBD30 /* Rx */, BF4A2F86225C8F160001B4CE /* UILocalizations.swift */, BF4A2FD32265F4020001B4CE /* UserDefaults.swift */, + BF8F6EDA2273453100B9447A /* Then.swift */, + BF8C6720227C2CF00012B5A8 /* Array.swift */, ); path = helpers; sourceTree = ""; @@ -369,6 +402,7 @@ isa = PBXGroup; children = ( BF42548E221EBB9800395AE6 /* LaunchScreen.storyboard */, + BFDE2601227B17D4008CBD30 /* AppServicesProvider.swift */, BF4A2F44225B751F0001B4CE /* AppFlow.swift */, BF425483221EBB9700395AE6 /* AppDelegate.swift */, ); @@ -396,7 +430,8 @@ isa = PBXGroup; children = ( BF425485221EBB9700395AE6 /* TasksListController.swift */, - BF794D522269C4FB000B19F3 /* TaskEditController.swift */, + BF8C6724227C3E310012B5A8 /* TaskCellController.swift */, + BF794D522269C4FB000B19F3 /* TaskController.swift */, ); path = Controllers; sourceTree = ""; @@ -405,7 +440,8 @@ isa = PBXGroup; children = ( BF4A2F7D225BB9270001B4CE /* TasksListReactor.swift */, - BF794D542269C505000B19F3 /* TaskEditReactor.swift */, + BF8C6722227C3E1E0012B5A8 /* TaskCellReactor.swift */, + BFE4BB46226CC7BD00739735 /* TaskReactor.swift */, ); path = Reactors; sourceTree = ""; @@ -426,19 +462,10 @@ path = Models; sourceTree = ""; }; - BF794D4722687254000B19F3 /* Storyboards */ = { - isa = PBXGroup; - children = ( - BF4A2F79225BB6FE0001B4CE /* TasksListController.storyboard */, - BF794D562269C51C000B19F3 /* TaskEditController.storyboard */, - ); - path = Storyboards; - sourceTree = ""; - }; BF794D4F226880ED000B19F3 /* Services */ = { isa = PBXGroup; children = ( - BF794D50226880FB000B19F3 /* TaskServices.swift */, + BF794D50226880FB000B19F3 /* TaskService.swift */, ); path = Services; sourceTree = ""; @@ -446,7 +473,7 @@ BF794D582269D028000B19F3 /* Controllers */ = { isa = PBXGroup; children = ( - BF425487221EBB9700395AE6 /* SecondViewController.swift */, + BF425487221EBB9700395AE6 /* SecondController.swift */, ); path = Controllers; sourceTree = ""; @@ -454,23 +481,15 @@ BF794D592269D030000B19F3 /* Flows */ = { isa = PBXGroup; children = ( - BF4A2F83225BBA4D0001B4CE /* SecondViewFlow.swift */, + BF4A2F83225BBA4D0001B4CE /* SecondFlow.swift */, ); path = Flows; sourceTree = ""; }; - BF794D5A2269D03C000B19F3 /* Storyboards */ = { - isa = PBXGroup; - children = ( - BF4A2F7B225BB89C0001B4CE /* SecondViewController.storyboard */, - ); - path = Storyboards; - sourceTree = ""; - }; BF794D5B2269D042000B19F3 /* Reactors */ = { isa = PBXGroup; children = ( - BF4A2F7F225BB9360001B4CE /* SecondViewReactor.swift */, + BF4A2F7F225BB9360001B4CE /* SecondReactor.swift */, ); path = Reactors; sourceTree = ""; @@ -478,7 +497,7 @@ BF794D5D2269D08A000B19F3 /* Controllers */ = { isa = PBXGroup; children = ( - BF4A2F5E225B7C200001B4CE /* OnBoardingIntroViewController.swift */, + BF4A2F5E225B7C200001B4CE /* OnBoardingController.swift */, ); path = Controllers; sourceTree = ""; @@ -491,20 +510,34 @@ path = Flows; sourceTree = ""; }; - BF794D5F2269D09C000B19F3 /* Storyboards */ = { + BF794D602269D0A3000B19F3 /* Reactors */ = { isa = PBXGroup; children = ( - BF4A2F5D225B7C200001B4CE /* OnboardingIntroViewController.storyboard */, + BF4A2F5F225B7C200001B4CE /* OnBoardingReactor.swift */, ); - path = Storyboards; + path = Reactors; sourceTree = ""; }; - BF794D602269D0A3000B19F3 /* Reactors */ = { + BF8C6728228016070012B5A8 /* ReusableKit */ = { isa = PBXGroup; children = ( - BF4A2F5F225B7C200001B4CE /* OnBoardingIntroViewReactor.swift */, + BF8C67292280176B0012B5A8 /* ReusableKit.swift */, + BF8C672B2280177E0012B5A8 /* UICollectionView+ReusableKit.swift */, + BF8C672D228017940012B5A8 /* UITableView+ReusableKit.swift */, ); - path = Reactors; + path = ReusableKit; + sourceTree = ""; + }; + BFDE25FC227B0522008CBD30 /* Rx */ = { + isa = PBXGroup; + children = ( + BFDE25FD227B052D008CBD30 /* NSViewController+Rx.swift */, + BFDE25FF227B053D008CBD30 /* UIViewController+Rx.swift */, + BF8C6726227C43E80012B5A8 /* Array+SectionModel.swift */, + BF8C6730228022140012B5A8 /* UICollectionView+RxReusableKit.swift */, + BF8C67322280222A0012B5A8 /* UITableView+RxReusableKit.swift */, + ); + path = Rx; sourceTree = ""; }; /* End PBXGroup section */ @@ -615,16 +648,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - BF794D482268750C000B19F3 /* TasksListController.storyboard in Resources */, BF4A2FCD2264A6810001B4CE /* production.json in Resources */, BF4A2FCE2264A6810001B4CE /* development.json in Resources */, BF4A2FAB225CA6DB0001B4CE /* LICENSE.md in Resources */, - BF4A2F8A225C9C830001B4CE /* OnboardingIntroViewController.storyboard in Resources */, BF4A2FA0225CA6DB0001B4CE /* KNOWLEDGES.md in Resources */, BF4A2FA9225CA6DB0001B4CE /* Readme.md in Resources */, BF425490221EBB9800395AE6 /* LaunchScreen.storyboard in Resources */, - BF4A2F7C225BB89C0001B4CE /* SecondViewController.storyboard in Resources */, - BF794D572269C51C000B19F3 /* TaskEditController.storyboard in Resources */, BF0CD6B622563CC200844F9A /* Localizable.strings in Resources */, BF4A2F9E225CA6DB0001B4CE /* CHANGELOG.md in Resources */, BF42548D221EBB9800395AE6 /* Assets.xcassets in Resources */, @@ -668,6 +697,8 @@ "$(SRCROOT)/Carthage/Build/iOS/SwiftyJSON.framework", "$(SRCROOT)/Carthage/Build/iOS/RxDataSources.framework", "$(SRCROOT)/Carthage/Build/iOS/Differentiator.framework", + "$(SRCROOT)/Carthage/Build/iOS/RxOptional.framework", + "$(SRCROOT)/Carthage/Build/iOS/SnapKit.framework", ); name = Carthage; outputFileListPaths = ( @@ -721,31 +752,46 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + BFDE25FE227B052D008CBD30 /* NSViewController+Rx.swift in Sources */, BF4A2F7E225BB9270001B4CE /* TasksListReactor.swift in Sources */, - BF4A2F6B225B7C200001B4CE /* OnBoardingIntroViewReactor.swift in Sources */, + BF4A2F6B225B7C200001B4CE /* OnBoardingReactor.swift in Sources */, BF4A2F4D225B79A80001B4CE /* Preferences.swift in Sources */, BF4A2FC0226479D20001B4CE /* Configuration.swift in Sources */, - BF794D3E22672474000B19F3 /* CoreTableViewCell.swift in Sources */, - BF425488221EBB9700395AE6 /* SecondViewController.swift in Sources */, - BF4A2F6A225B7C200001B4CE /* OnBoardingIntroViewController.swift in Sources */, + BF794D3E22672474000B19F3 /* CoreCellController.swift in Sources */, + BF8C6721227C2CF00012B5A8 /* Array.swift in Sources */, + BFDE2604227B185E008CBD30 /* CoreService.swift in Sources */, + BF425488221EBB9700395AE6 /* SecondController.swift in Sources */, + BF4A2F6A225B7C200001B4CE /* OnBoardingController.swift in Sources */, + BFE4BB47226CC7BD00739735 /* TaskReactor.swift in Sources */, + BF8C67332280222A0012B5A8 /* UITableView+RxReusableKit.swift in Sources */, BF425484221EBB9700395AE6 /* AppDelegate.swift in Sources */, + BFDE2600227B053D008CBD30 /* UIViewController+Rx.swift in Sources */, BF794D3A22671CD8000B19F3 /* TasksModel.swift in Sources */, - BF4A2F76225BB6190001B4CE /* CoreViewController.swift in Sources */, + BF4A2F76225BB6190001B4CE /* CoreController.swift in Sources */, + BF8C672E228017940012B5A8 /* UITableView+ReusableKit.swift in Sources */, + BF8C6725227C3E310012B5A8 /* TaskCellController.swift in Sources */, BF4A2FD02265D6330001B4CE /* ServicesProvider.swift in Sources */, - BF4A2F80225BB9360001B4CE /* SecondViewReactor.swift in Sources */, + BF4A2F80225BB9360001B4CE /* SecondReactor.swift in Sources */, + BFDE2602227B17D4008CBD30 /* AppServicesProvider.swift in Sources */, + BF8C6731228022140012B5A8 /* UICollectionView+RxReusableKit.swift in Sources */, BF0CD6AB22563C9800844F9A /* Strings.swift in Sources */, + BF8C6723227C3E1E0012B5A8 /* TaskCellReactor.swift in Sources */, BF4A2F87225C8F160001B4CE /* UILocalizations.swift in Sources */, + BFE4BB45226CAFB400739735 /* SyncState.swift in Sources */, + BF8C6727227C43E80012B5A8 /* Array+SectionModel.swift in Sources */, BF4A2F71225BB2CD0001B4CE /* CoreFlow.swift in Sources */, - BF794D532269C4FB000B19F3 /* TaskEditController.swift in Sources */, - BF794D51226880FB000B19F3 /* TaskServices.swift in Sources */, + BF794D532269C4FB000B19F3 /* TaskController.swift in Sources */, + BF8C672A2280176B0012B5A8 /* ReusableKit.swift in Sources */, + BF794D51226880FB000B19F3 /* TaskService.swift in Sources */, BF4A2FD42265F4020001B4CE /* UserDefaults.swift in Sources */, + BF8F6EDB2273453100B9447A /* Then.swift in Sources */, BF4A2F6F225BB2B10001B4CE /* OnBoardingFlow.swift in Sources */, BF4A2F45225B751F0001B4CE /* AppFlow.swift in Sources */, BF4A2FAE2260F0040001B4CE /* Logger.swift in Sources */, - BF794D552269C505000B19F3 /* TaskEditReactor.swift in Sources */, BF4A2F74225BB4B90001B4CE /* CoreSteps.swift in Sources */, BF4A2F82225BBA3F0001B4CE /* TasksFlow.swift in Sources */, - BF4A2F84225BBA4D0001B4CE /* SecondViewFlow.swift in Sources */, + BF4A2F84225BBA4D0001B4CE /* SecondFlow.swift in Sources */, + BF8C672C2280177E0012B5A8 /* UICollectionView+ReusableKit.swift in Sources */, BF425486221EBB9700395AE6 /* TasksListController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/waosSwift/config/headers/BridgingHeader.h b/waosSwift/config/headers/BridgingHeader.h index 09dc314..81cb1a8 100644 --- a/waosSwift/config/headers/BridgingHeader.h +++ b/waosSwift/config/headers/BridgingHeader.h @@ -1,4 +1,6 @@ @import RxSwift; +@import RxOptional; @import RxCocoa; @import RxFlow; @import SwiftyJSON; +@import SnapKit; diff --git a/waosSwift/config/localizations/Strings.swift b/waosSwift/config/localizations/Strings.swift index 186004e..cb7bb3c 100644 --- a/waosSwift/config/localizations/Strings.swift +++ b/waosSwift/config/localizations/Strings.swift @@ -11,16 +11,16 @@ import Foundation // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:disable nesting type_body_length type_name internal enum L10n { - /// title 1 - internal static let firstTitle = L10n.tr("Localizable", "first_title") /// this is the first page of the application, it will only be affixed once after installation. We call it onBoarding. internal static let onBoardingIntroduction = L10n.tr("Localizable", "onBoarding_introduction") /// onBoarding internal static let onBoardingTitle = L10n.tr("Localizable", "onBoarding_title") /// I'm in ! internal static let onBoardingValidation = L10n.tr("Localizable", "onBoarding_validation") - /// title 2 + /// Example internal static let secondTitle = L10n.tr("Localizable", "second_title") + /// Tasks + internal static let taskTitle = L10n.tr("Localizable", "task_title") } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable nesting type_body_length type_name diff --git a/waosSwift/config/localizations/en.lproj/Localizable.strings b/waosSwift/config/localizations/en.lproj/Localizable.strings index 4989409..b6371f4 100644 --- a/waosSwift/config/localizations/en.lproj/Localizable.strings +++ b/waosSwift/config/localizations/en.lproj/Localizable.strings @@ -8,7 +8,7 @@ "onBoarding_validation"= "I'm in !"; // first -"first_title"= "title 1"; +"task_title"= "Tasks"; // second -"second_title"= "title 2"; +"second_title"= "Example"; diff --git a/waosSwift/config/localizations/fr.lproj/Localizable.strings b/waosSwift/config/localizations/fr.lproj/Localizable.strings index 83c59cd..142d296 100644 --- a/waosSwift/config/localizations/fr.lproj/Localizable.strings +++ b/waosSwift/config/localizations/fr.lproj/Localizable.strings @@ -8,7 +8,7 @@ "onBoarding_validation"= "J'en suis !"; // first -"first_title"= "titre 1"; +"task_title"= "Tasks"; // second "second_title"= "titre 2"; diff --git a/waosSwift/lib/helpers/Array.swift b/waosSwift/lib/helpers/Array.swift new file mode 100644 index 0000000..385b9e4 --- /dev/null +++ b/waosSwift/lib/helpers/Array.swift @@ -0,0 +1,21 @@ +extension Array where Element: Equatable { + + @discardableResult mutating func remove(object: Element) -> Bool { + if let index = index(of: object) { + self.remove(at: index) + return true + } + return false + } + + @discardableResult mutating func remove(where predicate: (Array.Iterator.Element) -> Bool) -> Bool { + if let index = self.index(where: { (element) -> Bool in + return predicate(element) + }) { + self.remove(at: index) + return true + } + return false + } + +} diff --git a/waosSwift/lib/helpers/ReusableKit/ReusableKit.swift b/waosSwift/lib/helpers/ReusableKit/ReusableKit.swift new file mode 100644 index 0000000..4d6a34f --- /dev/null +++ b/waosSwift/lib/helpers/ReusableKit/ReusableKit.swift @@ -0,0 +1,66 @@ +#if os(iOS) +import UIKit + +public protocol CellType: class { + var reuseIdentifier: String? { get } +} + +/// A generic class that represents reusable cells. +public struct ReusableCell { + public typealias Class = Cell + + public let `class`: Class.Type = Class.self + public let identifier: String + public let nib: UINib? + + /// Create and returns a new `ReusableCell` instance. + /// + /// - parameter identifier: A reuse identifier. Use random UUID string if identifier is not provided. + /// - parameter nib: A `UINib` instance. Use this when registering from xib. + public init(identifier: String? = nil, nib: UINib? = nil) { + self.identifier = nib?.instantiate(withOwner: nil, options: nil).lazy + .compactMap { ($0 as? CellType)?.reuseIdentifier } + .first ?? identifier ?? UUID().uuidString + self.nib = nib + } + + /// A convenience initializer. + /// + /// - parameter identifier: A reuse identifier. Use random UUID string if identifier is not provided. + /// - parameter nibName: A name of nib. + public init(identifier: String? = nil, nibName: String) { + let nib = UINib(nibName: nibName, bundle: nil) + self.init(identifier: identifier, nib: nib) + } +} + +public protocol ViewType: class { +} + +/// A generic class that represents reusable views. +public struct ReusableView { + public typealias Class = View + + public let `class`: Class.Type = Class.self + public let identifier: String + public let nib: UINib? + + /// Create and returns a new `ReusableView` instance. + /// + /// - parameter identifier: A reuse identifier. Use random UUID string if identifier is not provided. + /// - parameter nib: A `UINib` instance. Use this when registering from xib. + public init(identifier: String? = nil, nib: UINib? = nil) { + self.identifier = identifier ?? UUID().uuidString + self.nib = nib + } + + /// A convenience initializer. + /// + /// - parameter identifier: A reuse identifier. Use random UUID string if identifier is not provided. + /// - parameter nibName: A name of nib. + public init(identifier: String? = nil, nibName: String) { + let nib = UINib(nibName: nibName, bundle: nil) + self.init(identifier: identifier, nib: nib) + } +} +#endif diff --git a/waosSwift/lib/helpers/ReusableKit/UICollectionView+ReusableKit.swift b/waosSwift/lib/helpers/ReusableKit/UICollectionView+ReusableKit.swift new file mode 100644 index 0000000..175089e --- /dev/null +++ b/waosSwift/lib/helpers/ReusableKit/UICollectionView+ReusableKit.swift @@ -0,0 +1,72 @@ +#if os(iOS) +import UIKit + +/// An enumeration that represents UICollectionView supplementary view kind. +public enum SupplementaryViewKind: String { + case header, footer + + public init?(rawValue: String) { + switch rawValue { + case UICollectionView.elementKindSectionHeader: self = .header + case UICollectionView.elementKindSectionFooter: self = .footer + default: return nil + } + } + + public var rawValue: String { + switch self { + case .header: return UICollectionView.elementKindSectionHeader + case .footer: return UICollectionView.elementKindSectionFooter + } + } +} + +extension UICollectionViewCell: CellType { +} + +extension UIView: ViewType { +} + +extension UICollectionView { + + // MARK: Cell + /// Registers a generic cell for use in creating new collection view cells. + public func register(_ cell: ReusableCell) { + if let nib = cell.nib { + self.register(nib, forCellWithReuseIdentifier: cell.identifier) + } else { + self.register(Cell.self, forCellWithReuseIdentifier: cell.identifier) + } + } + + /// Returns a generic reusable cell located by its identifier. + public func dequeue(_ cell: ReusableCell, for indexPath: IndexPath) -> Cell { + return self.dequeueReusableCell(withReuseIdentifier: cell.identifier, for: indexPath) as! Cell + } + + // MARK: Supplementary View + /// Registers a generic view for use in creating new supplementary views for the collection view. + public func register(_ view: ReusableView, kind: SupplementaryViewKind) { + self.register(view, kind: kind.rawValue) + } + + /// Registers a generic view for use in creating new supplementary views for the collection view. + public func register(_ view: ReusableView, kind: String) { + if let nib = view.nib { + self.register(nib, forSupplementaryViewOfKind: kind, withReuseIdentifier: view.identifier) + } else { + self.register(View.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: view.identifier) + } + } + + /// Returns a generic reusable supplementary view located by its identifier and kind. + public func dequeue(_ view: ReusableView, kind: String, for indexPath: IndexPath) -> View { + return self.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: view.identifier, for: indexPath) as! View + } + + /// Returns a generic reusable supplementary view located by its identifier and kind. + public func dequeue(_ view: ReusableView, kind: SupplementaryViewKind, for indexPath: IndexPath) -> View { + return self.dequeue(view, kind: kind.rawValue, for: indexPath) + } +} +#endif diff --git a/waosSwift/lib/helpers/ReusableKit/UITableView+ReusableKit.swift b/waosSwift/lib/helpers/ReusableKit/UITableView+ReusableKit.swift new file mode 100644 index 0000000..98b309f --- /dev/null +++ b/waosSwift/lib/helpers/ReusableKit/UITableView+ReusableKit.swift @@ -0,0 +1,44 @@ +#if os(iOS) +import UIKit + +extension UITableViewCell: CellType { +} + +extension UITableView { + + // MARK: Cell + /// Registers a generic cell for use in creating new table cells. + public func register(_ cell: ReusableCell) { + if let nib = cell.nib { + self.register(nib, forCellReuseIdentifier: cell.identifier) + } else { + self.register(Cell.self, forCellReuseIdentifier: cell.identifier) + } + } + + /// Returns a generic reusable cell located by its identifier. + public func dequeue(_ cell: ReusableCell) -> Cell? { + return self.dequeueReusableCell(withIdentifier: cell.identifier) as? Cell + } + + /// Returns a generic reusable cell located by its identifier. + public func dequeue(_ cell: ReusableCell, for indexPath: IndexPath) -> Cell { + return self.dequeueReusableCell(withIdentifier: cell.identifier, for: indexPath) as! Cell + } + + // MARK: View + /// Registers a generic view for use in creating new table header or footer views. + public func register(_ cell: ReusableView) { + if let nib = cell.nib { + self.register(nib, forHeaderFooterViewReuseIdentifier: cell.identifier) + } else { + self.register(View.self, forHeaderFooterViewReuseIdentifier: cell.identifier) + } + } + + /// Returns a generic reusable header of footer view located by its identifier. + public func dequeue(_ view: ReusableView) -> View? { + return self.dequeueReusableHeaderFooterView(withIdentifier: view.identifier) as? View + } +} +#endif diff --git a/waosSwift/lib/helpers/Rx/Array+SectionModel.swift b/waosSwift/lib/helpers/Rx/Array+SectionModel.swift new file mode 100644 index 0000000..34981da --- /dev/null +++ b/waosSwift/lib/helpers/Rx/Array+SectionModel.swift @@ -0,0 +1,40 @@ +import RxDataSources + +extension Array where Element: SectionModelType { + + public subscript(indexPath: IndexPath) -> Element.Item { + get { + return self[indexPath.section].items[indexPath.item] + } + mutating set { + self.update(section: indexPath.section) { items in + items[indexPath.item] = newValue + } + } + } + + public mutating func insert(_ newElement: Element.Item, at indexPath: IndexPath) { + self.update(section: indexPath.section) { items in + items.insert(newElement, at: indexPath.item) + } + } + + @discardableResult + public mutating func remove(at indexPath: IndexPath) -> Element.Item { + return self.update(section: indexPath.section) { items in + return items.remove(at: indexPath.item) + } + } + + private mutating func replace(section: Int, items: [Element.Item]) { + self[section] = Element.init(original: self[section], items: items) + } + + private mutating func update(section: Int, mutate: (inout [Element.Item]) -> T) -> T { + var items = self[section].items + let value = mutate(&items) + self[section] = Element.init(original: self[section], items: items) + return value + } + +} diff --git a/waosSwift/lib/helpers/Rx/NSViewController+Rx.swift b/waosSwift/lib/helpers/Rx/NSViewController+Rx.swift new file mode 100644 index 0000000..6e0362a --- /dev/null +++ b/waosSwift/lib/helpers/Rx/NSViewController+Rx.swift @@ -0,0 +1,40 @@ +#if os(macOS) +import AppKit + +import RxCocoa +import RxSwift + +public extension Reactive where Base: NSViewController { + var viewDidLoad: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in } + return ControlEvent(events: source) + } + + var viewWillAppear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewWillAppear)).map { _ in } + return ControlEvent(events: source) + } + var viewDidAppear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidAppear)).map { _ in } + return ControlEvent(events: source) + } + + var viewWillDisappear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewWillDisappear)).map { _ in } + return ControlEvent(events: source) + } + var viewDidDisappear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidDisappear)).map { _ in } + return ControlEvent(events: source) + } + + var viewWillLayout: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewWillLayout)).map { _ in } + return ControlEvent(events: source) + } + var viewDidLayout: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidLayout)).map { _ in } + return ControlEvent(events: source) + } +} +#endif diff --git a/waosSwift/lib/helpers/Rx/UICollectionView+RxReusableKit.swift b/waosSwift/lib/helpers/Rx/UICollectionView+RxReusableKit.swift new file mode 100644 index 0000000..736d91e --- /dev/null +++ b/waosSwift/lib/helpers/Rx/UICollectionView+RxReusableKit.swift @@ -0,0 +1,21 @@ +import RxCocoa +import RxSwift + +#if os(iOS) +import UIKit + +extension Reactive where Base: UICollectionView { + public func items( + _ reusableCell: ReusableCell + ) -> (_ source: O) + -> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void) + -> Disposable + where O.E == S { + return { source in + return { configureCell in + return self.items(cellIdentifier: reusableCell.identifier, cellType: Cell.self)(source)(configureCell) + } + } + } +} +#endif diff --git a/waosSwift/lib/helpers/Rx/UITableView+RxReusableKit.swift b/waosSwift/lib/helpers/Rx/UITableView+RxReusableKit.swift new file mode 100644 index 0000000..2dbcef9 --- /dev/null +++ b/waosSwift/lib/helpers/Rx/UITableView+RxReusableKit.swift @@ -0,0 +1,21 @@ +import RxCocoa +import RxSwift + +#if os(iOS) +import UIKit + +extension Reactive where Base: UITableView { + public func items( + _ reusableCell: ReusableCell + ) -> (_ source: O) + -> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void) + -> Disposable + where O.E == S { + return { source in + return { configureCell in + return self.items(cellIdentifier: reusableCell.identifier, cellType: Cell.self)(source)(configureCell) + } + } + } +} +#endif diff --git a/waosSwift/lib/helpers/Rx/UIViewController+Rx.swift b/waosSwift/lib/helpers/Rx/UIViewController+Rx.swift new file mode 100644 index 0000000..bff0f83 --- /dev/null +++ b/waosSwift/lib/helpers/Rx/UIViewController+Rx.swift @@ -0,0 +1,68 @@ +#if os(iOS) || os(tvOS) +import UIKit + +import RxCocoa +import RxSwift + +public extension Reactive where Base: UIViewController { + var viewDidLoad: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in } + return ControlEvent(events: source) + } + + var viewWillAppear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewWillAppear)).map { $0.first as? Bool ?? false } + return ControlEvent(events: source) + } + var viewDidAppear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidAppear)).map { $0.first as? Bool ?? false } + return ControlEvent(events: source) + } + + var viewWillDisappear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewWillDisappear)).map { $0.first as? Bool ?? false } + return ControlEvent(events: source) + } + var viewDidDisappear: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidDisappear)).map { $0.first as? Bool ?? false } + return ControlEvent(events: source) + } + + var viewWillLayoutSubviews: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewWillLayoutSubviews)).map { _ in } + return ControlEvent(events: source) + } + var viewDidLayoutSubviews: ControlEvent { + let source = self.methodInvoked(#selector(Base.viewDidLayoutSubviews)).map { _ in } + return ControlEvent(events: source) + } + + var willMoveToParentViewController: ControlEvent { + let source = self.methodInvoked(#selector(Base.willMove)).map { $0.first as? UIViewController } + return ControlEvent(events: source) + } + var didMoveToParentViewController: ControlEvent { + let source = self.methodInvoked(#selector(Base.didMove)).map { $0.first as? UIViewController } + return ControlEvent(events: source) + } + + var didReceiveMemoryWarning: ControlEvent { + let source = self.methodInvoked(#selector(Base.didReceiveMemoryWarning)).map { _ in } + return ControlEvent(events: source) + } + + /// Rx observable, triggered when the ViewController appearance state changes (true if the View is being displayed, false otherwise) + var isVisible: Observable { + let viewDidAppearObservable = self.base.rx.viewDidAppear.map { _ in true } + let viewWillDisappearObservable = self.base.rx.viewWillDisappear.map { _ in false } + return Observable.merge(viewDidAppearObservable, viewWillDisappearObservable) + } + + /// Rx observable, triggered when the ViewController is being dismissed + var isDismissing: ControlEvent { + let source = self.sentMessage(#selector(Base.dismiss)).map { $0.first as? Bool ?? false } + return ControlEvent(events: source) + } + +} +#endif diff --git a/waosSwift/lib/helpers/Then.swift b/waosSwift/lib/helpers/Then.swift new file mode 100644 index 0000000..b77a4d8 --- /dev/null +++ b/waosSwift/lib/helpers/Then.swift @@ -0,0 +1,85 @@ +// The MIT License (MIT) +// +// Copyright (c) 2015 Suyeol Jeon (xoul.kr) +// +// 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: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// 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. + +import Foundation +import CoreGraphics +#if os(iOS) || os(tvOS) +import UIKit.UIGeometry +#endif + +public protocol Then {} + +extension Then where Self: Any { + + /// Makes it available to set properties with closures just after initializing and copying the value types. + /// + /// let frame = CGRect().with { + /// $0.origin.x = 10 + /// $0.size.width = 100 + /// } + public func with(_ block: (inout Self) throws -> Void) rethrows -> Self { + var copy = self + try block(©) + return copy + } + + /// Makes it available to execute something with closures. + /// + /// UserDefaults.standard.do { + /// $0.set("devxoul", forKey: "username") + /// $0.set("devxoul@gmail.com", forKey: "email") + /// $0.synchronize() + /// } + public func `do`(_ block: (Self) throws -> Void) rethrows { + try block(self) + } + +} + +extension Then where Self: AnyObject { + + /// Makes it available to set properties with closures just after initializing. + /// + /// let label = UILabel().then { + /// $0.textAlignment = .Center + /// $0.textColor = UIColor.blackColor() + /// $0.text = "Hello, World!" + /// } + public func then(_ block: (Self) throws -> Void) rethrows -> Self { + try block(self) + return self + } + +} + +extension NSObject: Then {} + +extension CGPoint: Then {} +extension CGRect: Then {} +extension CGSize: Then {} +extension CGVector: Then {} + +#if os(iOS) || os(tvOS) +extension UIEdgeInsets: Then {} +extension UIOffset: Then {} +extension UIRectEdge: Then {} +#endif diff --git a/waosSwift/lib/services/SyncState.swift b/waosSwift/lib/services/SyncState.swift new file mode 100644 index 0000000..a7256ab --- /dev/null +++ b/waosSwift/lib/services/SyncState.swift @@ -0,0 +1,4 @@ +struct SynchronisationState { + var tasks: [Task] +} +let syncState = Variable(SynchronisationState(tasks: [])) diff --git a/waosSwift/modules/Tasks/Controllers/TaskCellController.swift b/waosSwift/modules/Tasks/Controllers/TaskCellController.swift new file mode 100644 index 0000000..403357c --- /dev/null +++ b/waosSwift/modules/Tasks/Controllers/TaskCellController.swift @@ -0,0 +1,48 @@ +/** + * Dependencies + */ + +import UIKit +import ReactorKit + +/** + * Controller + */ + +final class TaskCellController: CoreCellController, View { + + typealias Reactor = TaskCellReactor + + // MARK: UI + + let titleLabel = UILabel().then { + $0.textColor = .black + $0.numberOfLines = 2 + } + + // MARK: Initializing + + override func initialize() { + self.contentView.addSubview(self.titleLabel) + } + + // MARK: Binding + + func bind(reactor: Reactor) { + self.titleLabel.text = reactor.currentState.title + } + + // MARK: Layout + + override func layoutSubviews() { + super.layoutSubviews() + + self.titleLabel.snp.makeConstraints { make in + make.left.equalTo(25) + make.right.equalTo(25) + make.top.equalTo(25) + make.height.equalTo(25) + } + self.titleLabel.sizeToFit() + } +} diff --git a/waosSwift/modules/Tasks/Controllers/TaskController.swift b/waosSwift/modules/Tasks/Controllers/TaskController.swift new file mode 100644 index 0000000..601ce1d --- /dev/null +++ b/waosSwift/modules/Tasks/Controllers/TaskController.swift @@ -0,0 +1,126 @@ +/** + * Dependencies + */ + +import UIKit +import ReactorKit + +/** + * Controller + */ + +final class TaskController: CoreController, View { + + // MARK: Constants + + let mode: Mode + + // MARK: UI + + // edit + let titleInput = UITextField().then { + $0.autocorrectionType = .no + $0.borderStyle = .roundedRect + $0.placeholder = "Do something..." + } + let cancelButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: nil, action: nil) + let doneButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: nil, action: nil) + + // MARK: Initializing + + init(reactor: TaskReactor) { + self.mode = reactor.currentState.mode + super.init() + self.navigationItem.leftBarButtonItem = self.cancelButtonItem + self.navigationItem.rightBarButtonItem = self.doneButtonItem + self.reactor = reactor + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .white + self.view.addSubview(self.titleInput) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.titleInput.becomeFirstResponder() + } + + override func setupConstraints() { + titleInput.snp.makeConstraints { make in + make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top).offset(50) + make.height.equalTo(50) + make.left.equalTo(50) + make.right.equalTo(-50) + } + } + + // MARK: Binding + + func bind(reactor: TaskReactor) { + bindAction(reactor) + bindState(reactor) + bindView(reactor) + } +} + +/** + * Extensions + */ + +private extension TaskController { + + // MARK: views (View -> View) + + func bindView(_ reactor: TaskReactor) { + // cancel + self.cancelButtonItem.rx.tap + .subscribe(onNext: { [weak self] _ in + guard let `self` = self else { return } + self.dismiss(animated: true, completion: nil) + }) + .disposed(by: self.disposeBag) + } + + // MARK: actions (View -> Reactor) + + func bindAction(_ reactor: TaskReactor) { + self.doneButtonItem.rx.tap + .map { Reactor.Action.done } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + + self.titleInput.rx.text + .filterNil() + .map(Reactor.Action.updateTitle) + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + } + + // MARK: states (Reactor -> View) + + func bindState(_ reactor: TaskReactor) { + // title + reactor.state.asObservable() + .map { $0.task.title } + .distinctUntilChanged() + .bind(to: self.titleInput.rx.text) + .disposed(by: self.disposeBag) + // dissmiss + reactor.state.asObservable() + .map { $0.isDismissed } + .distinctUntilChanged() + .filter { $0 } + .subscribe(onNext: { [weak self] _ in + self?.dismiss(animated: true, completion: nil) + }) + .disposed(by: self.disposeBag) + } +} diff --git a/waosSwift/modules/Tasks/Controllers/TaskEditController.swift b/waosSwift/modules/Tasks/Controllers/TaskEditController.swift deleted file mode 100644 index 11d361b..0000000 --- a/waosSwift/modules/Tasks/Controllers/TaskEditController.swift +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Dependencies - */ - -import UIKit -import Reusable -import ReactorKit - -/** - * Controller - */ - -class TaskEditController: CoreViewController, StoryboardView, StoryboardBased { - - // MARK: Initializing - - override func viewDidLoad() { - super.viewDidLoad() - } - - // MARK: Binding - - func bind(reactor: TaskEditReactor) { - bindAction(reactor) - bindState(reactor) - bindView(reactor) - } -} - -/** - * Extensions - */ - -private extension TaskEditController { - - // MARK: views (View -> View) - - func bindView(_ reactor: TaskEditReactor) {} - - // MARK: actions (View -> Reactor) - - func bindAction(_ reactor: TaskEditReactor) {} - - // MARK: states (Reactor -> View) - - func bindState(_ reactor: TaskEditReactor) {} -} diff --git a/waosSwift/modules/Tasks/Controllers/TasksListController.swift b/waosSwift/modules/Tasks/Controllers/TasksListController.swift index 55ae465..2a5b07b 100644 --- a/waosSwift/modules/Tasks/Controllers/TasksListController.swift +++ b/waosSwift/modules/Tasks/Controllers/TasksListController.swift @@ -9,71 +9,67 @@ import ReactorKit import RxDataSources /** - * Section + * Controller */ -struct MySection { - var header: String - var items: [Task] -} - -extension MySection: AnimatableSectionModelType { - typealias Item = Task - var identity: String { - return header - } - init(original: MySection, items: [Task]) { - self = original - self.items = items - } -} - -extension Task: IdentifiableType, Equatable { - typealias Identity = String - var identity: Identity { return id } -} +final class TasksListController: CoreController, View { -func ==(lhs: Task, rhs: Task) -> Bool { - return lhs.id == rhs.id -} + // MARK: Constants -/** - * Controller - */ - -class TasksListController: CoreViewController, StoryboardView, StoryboardBased { + struct Reusable { + static let taskCell = ReusableCell() + } // MARK: UI - @IBOutlet var tableView: UITableView! + let tableView = UITableView().then { + $0.register(Reusable.taskCell) + $0.allowsSelectionDuringEditing = true + $0.rowHeight = 75 + } + let addButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil) // MARK: Properties - let dataSource = RxTableViewSectionedAnimatedDataSource( - configureCell: { ds, tv, _, item in - let cell = tv.dequeueReusableCell(withIdentifier: "Cell") ?? UITableViewCell(style: .default, reuseIdentifier: "Cell") - cell.textLabel?.text = item.title + let dataSource = RxTableViewSectionedReloadDataSource( + configureCell: { _, tableView, indexPath, reactor in + let cell = tableView.dequeue(Reusable.taskCell, for: indexPath) + cell.reactor = reactor return cell - }, titleForHeaderInSection: { ds, index in return ds.sectionModels[index].header }) - let addButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil) + }) // MARK: Initializing + init(reactor: TasksListReactor) { + super.init() + self.navigationItem.rightBarButtonItem = self.addButtonItem + self.reactor = reactor + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: View Life Cycle + override func viewDidLoad() { super.viewDidLoad() - // self.navigationItem.leftBarButtonItem = self.editButtonItem - self.navigationItem.rightBarButtonItem = self.addButtonItem - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") - tableView.allowsSelectionDuringEditing = true - self.reactor = TasksListReactor() // inject reactor + self.view.backgroundColor = .white + self.view.addSubview(self.tableView) + } + + override func setupConstraints() { + self.tableView.snp.makeConstraints { make in + make.edges.equalTo(0) + } } // MARK: Binding func bind(reactor: TasksListReactor) { + bindView(reactor) bindAction(reactor) bindState(reactor) - bindView(reactor) } } @@ -82,11 +78,26 @@ private extension TasksListController { // MARK: views (View -> View) func bindView(_ reactor: TasksListReactor) { + self.tableView.rx.setDelegate(self).disposed(by: self.disposeBag) + self.dataSource.canEditRowAtIndexPath = { _, _ in true } + // add - addButtonItem.rx.tap - .subscribe(onNext: { [weak self] _ in + self.tableView.rx.modelSelected(type(of: self.dataSource).Section.Item.self) + .map(reactor.editReactor) + .subscribe(onNext: { [weak self] reactor in guard let `self` = self else { return } - let viewController = TaskEditController() + let viewController = TaskController(reactor: reactor) + viewController.titleInput.text = reactor.initialState.task.title + let navigationController = UINavigationController(rootViewController: viewController) + self.present(navigationController, animated: true, completion: nil) + }) + .disposed(by: self.disposeBag) + // add + self.addButtonItem.rx.tap + .map(reactor.addReactor) + .subscribe(onNext: { [weak self] reactor in + guard let `self` = self else { return } + let viewController = TaskController(reactor: reactor) let navigationController = UINavigationController(rootViewController: viewController) self.present(navigationController, animated: true, completion: nil) }) @@ -97,17 +108,22 @@ private extension TasksListController { func bindAction(_ reactor: TasksListReactor) { // viewDidLoad - Observable.just(Void()) + self.rx.viewDidLoad .map { Reactor.Action.get } .bind(to: reactor.action) .disposed(by: self.disposeBag) + // delete + self.tableView.rx.itemDeleted + .map(Reactor.Action.delete) + .bind(to: reactor.action) + .disposed(by: self.disposeBag) } // MARK: states (Reactor -> View) func bindState(_ reactor: TasksListReactor) { // data - reactor.state.asObservable().map { [MySection(header: "", items: $0.tasks)] } + reactor.state.asObservable().map { $0.tasks } .bind(to: self.tableView.rx.items(dataSource: self.dataSource)) .disposed(by: self.disposeBag) } diff --git a/waosSwift/modules/Tasks/Flows/TasksFlow.swift b/waosSwift/modules/Tasks/Flows/TasksFlow.swift index f5f3757..0d8b12c 100644 --- a/waosSwift/modules/Tasks/Flows/TasksFlow.swift +++ b/waosSwift/modules/Tasks/Flows/TasksFlow.swift @@ -9,7 +9,7 @@ import UIKit * Flow */ -final class TasksViewFlow: Flow { +final class TasksFlow: Flow { var root: Presentable { return self.rootViewController } @@ -29,17 +29,19 @@ final class TasksViewFlow: Flow { guard let step = step as? SampleStep else { return FlowContributors.none } switch step { - case .tasksListIsRequired: - return navigateToTasksViewScreen() + case .tasksIsRequired: + return navigateToTasksScreen() default: return FlowContributors.none } } - private func navigateToTasksViewScreen() -> FlowContributors { - let viewController = TasksListController.instantiate() - viewController.title = L10n.firstTitle + private func navigateToTasksScreen() -> FlowContributors { + let provider = AppServicesProvider() + let reactor = TasksListReactor(provider: provider) + let viewController = TasksListController(reactor: reactor) + viewController.title = L10n.taskTitle self.rootViewController.pushViewController(viewController, animated: true) - return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: OneStepper(withSingleStep: SampleStep.tasksListIsRequired))) + return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: OneStepper(withSingleStep: SampleStep.tasksIsRequired))) } } diff --git a/waosSwift/modules/Tasks/Reactors/TaskCellReactor.swift b/waosSwift/modules/Tasks/Reactors/TaskCellReactor.swift new file mode 100644 index 0000000..987719f --- /dev/null +++ b/waosSwift/modules/Tasks/Reactors/TaskCellReactor.swift @@ -0,0 +1,28 @@ +/** + * Dependencies + */ + +import ReactorKit + +/** + * Reactor + */ + +final class TaskCellReactor: Reactor { + + // MARK: Constants + + // user actions + typealias Action = NoAction + + // MARK: Properties + + let initialState: Task + + // MARK: Initialization + + init(task: Task) { + self.initialState = task + } + +} diff --git a/waosSwift/modules/Tasks/Reactors/TaskEditReactor.swift b/waosSwift/modules/Tasks/Reactors/TaskEditReactor.swift deleted file mode 100644 index 821f541..0000000 --- a/waosSwift/modules/Tasks/Reactors/TaskEditReactor.swift +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Dependencies - */ - -import ReactorKit - -/** - * Reactor - */ - -final class TaskEditReactor: Reactor { - - // MARK: Constants - - // user actions - enum Action { - } - - // state changes - enum Mutation { - } - - // the current view state - struct State { - } - - // MARK: Properties - - let initialState = State() - - // MARK: Action -> Mutation (mutate() receives an Action and generates an Observable) -// -// func mutate(action: Action) -> Observable { -// switch action { -// -// } -// } - - // MARK: Mutation -> State (reduce() generates a new State from a previous State and a Mutation) - -// func reduce(state: State, mutation: Mutation) -> State { -// var state = state -// switch mutation { -// -// } -// return state -// } - -} diff --git a/waosSwift/modules/Tasks/Reactors/TaskReactor.swift b/waosSwift/modules/Tasks/Reactors/TaskReactor.swift new file mode 100644 index 0000000..288aa4a --- /dev/null +++ b/waosSwift/modules/Tasks/Reactors/TaskReactor.swift @@ -0,0 +1,108 @@ +/** + * Dependencies + */ + +import ReactorKit + +enum Mode { + case add + case view(Task) + case edit(Task) +} + +/** + * Reactor + */ + +final class TaskReactor: Reactor { + + // MARK: Constants + + // user actions + enum Action { + case updateTitle(String) + case done + } + + // state changes + enum Mutation { + case updateTitle(String) + case dismiss + } + + // the current view state + struct State { + var task: Task + var isDismissed: Bool + var mode: Mode + + init(task: Task, mode: Mode) { + self.task = task + self.isDismissed = false + self.mode = mode + } + } + + // MARK: Properties + + let provider: AppServicesProviderType + let mode: Mode + let initialState: State + + // MARK: Initialization + + init(provider: AppServicesProviderType, mode: Mode) { + self.provider = provider + self.mode = mode + + switch mode { + case .add: + self.initialState = State(task: Task(id: "", title: ""), mode: mode) + case .view(let task): + self.initialState = State(task: task, mode: mode) + case .edit(let task): + print(task) + self.initialState = State(task: task, mode: mode) + print("test 0 \(self.currentState)") + } + } + + // MARK: Action -> Mutation (mutate() receives an Action and generates an Observable) + + func mutate(action: Action) -> Observable { + switch action { + // updateTitle + case let .updateTitle(title): + return .just(.updateTitle(title)) + // done + case .done: + print("done \(mode) => \(self.currentState.task)") + switch mode { + case .add: + return self.provider.taskService + .create(title: self.currentState.task.title) + .map { _ in .dismiss } + case .view: + return Observable.just(Mutation.dismiss) + case .edit: + return self.provider.taskService + .save(task: self.currentState.task) + .map { _ in .dismiss } + } + } + } + + // MARK: Mutation -> State (reduce() generates a new State from a previous State and a Mutation) + + func reduce(state: State, mutation: Mutation) -> State { + var state = state + switch mutation { + case let .updateTitle(title): + state.task.title = title + return state + case .dismiss: + state.isDismissed = true + return state + } + } +} diff --git a/waosSwift/modules/Tasks/Reactors/TasksListReactor.swift b/waosSwift/modules/Tasks/Reactors/TasksListReactor.swift index 45582a0..2bdcff0 100644 --- a/waosSwift/modules/Tasks/Reactors/TasksListReactor.swift +++ b/waosSwift/modules/Tasks/Reactors/TasksListReactor.swift @@ -3,63 +3,86 @@ */ import ReactorKit +import Differentiator /** * Reactor */ +typealias Sections = SectionModel + final class TasksListReactor: Reactor { // MARK: Constants // user actions enum Action { + case refresh([Task]) case get - case create + case delete(IndexPath) } // state changes enum Mutation { - case createTask - case getTasks([Task]) - case setLoading(Bool) + case set([Sections]) } // the current view state struct State { - var isLoading: Bool - var tasks: [Task] + var tasks: [Sections] init() { - self.isLoading = false - self.tasks = [] + self.tasks = [Sections(model: Void(), items: [])] } } // MARK: Properties - let taskService = TaskService() - let initialState = State() + let provider: AppServicesProviderType + let initialState: State + + // MARK: Initialization + + init(provider: AppServicesProviderType) { + self.provider = provider + self.initialState = State() + } + + // MARK: Transform -> Merges two observables into a single observabe : 1. Mutation observable from Reactor 2. Mutation observable from global state + + func transform(action: Observable) -> Observable { + let refresh = self.provider.taskService.tasks + .map { Action.refresh($0 ?? [ ]) } + return Observable.of(action, refresh).merge() + } // MARK: Action -> Mutation (mutate() receives an Action and generates an Observable) func mutate(action: Action) -> Observable { switch action { + // refresh + case let .refresh(tasks): + print("Action -> Mutation refresh") + let items = tasks.map(TaskCellReactor.init) + let section = Sections(model: Void(), items: items) + return .just(.set([section])) // get case .get: - return Observable.concat([ - Observable.just(Mutation.setLoading(true)), - self.taskService.get() - .map { Mutation.getTasks($0) }, - Observable.just(Mutation.setLoading(false)) - ]) - // create - case .create: - return Observable.concat([ - Observable.just(Mutation.setLoading(true)), - Observable.just(Mutation.createTask).delay(0.1, scheduler: MainScheduler.instance), - Observable.just(Mutation.setLoading(false)) - ]) + print("Action -> Mutation get") + return self.provider.taskService + .get() + .map { tasks in + let items = tasks.map(TaskCellReactor.init) + let section = Sections(model: Void(), items: items) + return .set([section]) + } + // delete + case let .delete(i): + print("Action -> Mutation delete") + let task = self.currentState.tasks[i].currentState + return self.provider.taskService + .delete(task: task) + .flatMap { _ in Observable.empty() } } } @@ -68,19 +91,27 @@ final class TasksListReactor: Reactor { func reduce(state: State, mutation: Mutation) -> State { var state = state switch mutation { - // get - case let .getTasks(tasks): - var newState = state - newState.tasks = tasks - return newState - // create - case .createTask: - print("create Task") - // loading - case let .setLoading(isLoading): - state.isLoading = isLoading + // set + case let .set(tasks): + print("Mutation -> State set") + state.tasks = tasks + return state } - return state } + // reactor init + + func viewReactor(_ taskCellReactor: TaskCellReactor) -> TaskReactor { + let task = taskCellReactor.currentState + return TaskReactor(provider: self.provider, mode: .view(task)) + } + + func addReactor() -> TaskReactor { + return TaskReactor(provider: self.provider, mode: .add) + } + + func editReactor(_ taskCellReactor: TaskCellReactor) -> TaskReactor { + let task = taskCellReactor.currentState + return TaskReactor(provider: self.provider, mode: .edit(task)) + } } diff --git a/waosSwift/modules/Tasks/Services/TaskService.swift b/waosSwift/modules/Tasks/Services/TaskService.swift new file mode 100644 index 0000000..da21dea --- /dev/null +++ b/waosSwift/modules/Tasks/Services/TaskService.swift @@ -0,0 +1,68 @@ +/** + * Dependencies + */ + +import ReactorKit + +//enum TaskEvent { +// case refresh([Task]) +//} + +/** + * Service + */ + +protocol TaskServiceType { + var tasks: Observable<[Task]?> { get } + + func get() -> Observable<[Task]> + func create(title: String) -> Observable + func save(task: Task) -> Observable + func delete(task: Task) -> Observable +} + +final class TaskService: CoreService, TaskServiceType { + // temporary array + var defaultTasks: [Task] = [ + Task(id: "572f16439c0d3ffe0bc084a4", title: "Task one from service"), + Task(id: "572f16439c0d3ffe0bc084a5", title: "Task two from service"), + Task(id: "572f16439c0d3ffe0bc084a6", title: "Task three from service") + ] + + fileprivate let tasksSubject = ReplaySubject<[Task]?>.create(bufferSize: 1) + lazy var tasks: Observable<[Task]?> = self.tasksSubject.asObservable() + .startWith(nil) + .share(replay: 1) + + func get() -> Observable<[Task]> { + print("service get") + return .just(self.defaultTasks) + } + + func create(title: String) -> Observable { + print("service create") + let newTask = Task(id: "572f16439c0d3ffe0bc084a7", title: title) + self.defaultTasks.append(newTask) + self.tasksSubject.onNext(self.defaultTasks) + return .just(newTask) + } + + func save(task: Task) -> Observable { + print("service save") + let newTask = Task(id: task.id, title: task.title) + if let index = self.defaultTasks.firstIndex(where: { $0.id == task.id }) { + self.defaultTasks[index] = newTask + } + self.tasksSubject.onNext(self.defaultTasks) + return .just(newTask) + } + + func delete(task: Task) -> Observable { + print("service delete") + if let index = self.defaultTasks.firstIndex(where: { $0.id == task.id }) { + self.defaultTasks.remove(at: index) + } + self.tasksSubject.onNext(self.defaultTasks) + return .just(Void()) + } +} diff --git a/waosSwift/modules/Tasks/Services/TaskServices.swift b/waosSwift/modules/Tasks/Services/TaskServices.swift deleted file mode 100644 index d9566cb..0000000 --- a/waosSwift/modules/Tasks/Services/TaskServices.swift +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Dependencies - */ - -import RxSwift - -/** - * Service - */ - -protocol TaskServiceType { - func get() -> Observable<[Task]> -} - -final class TaskService: TaskServiceType { - func get() -> Observable<[Task]> { - let defaultTasks: [Task] = [ - Task(id: "572f16439c0d3ffe0bc084a4", title: "Task one from service"), - Task(id: "572f16439c0d3ffe0bc084a5", title: "Task two from service"), - Task(id: "572f16439c0d3ffe0bc084a6", title: "Task three from service") - ] - return .just(defaultTasks) - } -} diff --git a/waosSwift/modules/Tasks/Storyboards/TaskEditController.storyboard b/waosSwift/modules/Tasks/Storyboards/TaskEditController.storyboard deleted file mode 100644 index 8d5c352..0000000 --- a/waosSwift/modules/Tasks/Storyboards/TaskEditController.storyboard +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/waosSwift/modules/Tasks/Storyboards/TasksListController.storyboard b/waosSwift/modules/Tasks/Storyboards/TasksListController.storyboard deleted file mode 100644 index 62a9262..0000000 --- a/waosSwift/modules/Tasks/Storyboards/TasksListController.storyboard +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/waosSwift/modules/app/AppDelegate.swift b/waosSwift/modules/app/AppDelegate.swift index 240b939..accb03d 100644 --- a/waosSwift/modules/app/AppDelegate.swift +++ b/waosSwift/modules/app/AppDelegate.swift @@ -1,4 +1,6 @@ import UIKit +import ReactorKit +import RxSwift @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { diff --git a/waosSwift/modules/app/AppServicesProvider.swift b/waosSwift/modules/app/AppServicesProvider.swift new file mode 100644 index 0000000..dfa5ab5 --- /dev/null +++ b/waosSwift/modules/app/AppServicesProvider.swift @@ -0,0 +1,7 @@ +protocol AppServicesProviderType: class { + var taskService: TaskServiceType { get } +} + +final class AppServicesProvider: AppServicesProviderType { + lazy var taskService: TaskServiceType = TaskService(provider: self) +} diff --git a/waosSwift/modules/core/CoreTableViewCell.swift b/waosSwift/modules/core/CoreCellController.swift similarity index 82% rename from waosSwift/modules/core/CoreTableViewCell.swift rename to waosSwift/modules/core/CoreCellController.swift index d17e8ee..bda456b 100644 --- a/waosSwift/modules/core/CoreTableViewCell.swift +++ b/waosSwift/modules/core/CoreCellController.swift @@ -1,10 +1,8 @@ import UIKit -import RxSwift +class CoreCellController: UITableViewCell { -class CoreTableViewCell: UITableViewCell { - - var disposeBag: DisposeBag = DisposeBag() + // MARK: Initializing override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -19,4 +17,8 @@ class CoreTableViewCell: UITableViewCell { // Override point } + // MARK: Rx + + var disposeBag: DisposeBag = DisposeBag() + } diff --git a/waosSwift/modules/core/CoreController.swift b/waosSwift/modules/core/CoreController.swift new file mode 100644 index 0000000..1dba1b8 --- /dev/null +++ b/waosSwift/modules/core/CoreController.swift @@ -0,0 +1,46 @@ +import UIKit + +class CoreController: UIViewController { + lazy private(set) var className: String = { + return type(of: self).description().components(separatedBy: ".").last ?? "" + }() + + // MARK: Initializing + init() { + super.init(nibName: nil, bundle: nil) + } + + required convenience init?(coder aDecoder: NSCoder) { + self.init() + } + + // MARK: Rx + + var disposeBag = DisposeBag() + + // MARK: deinit + + deinit { + log.info("DEINIT: \(self.className)") + } + + // MARK: Layout Constraints + + private(set) var didSetupConstraints = false + + override func viewDidLoad() { + self.view.setNeedsUpdateConstraints() + } + + override func updateViewConstraints() { + if !self.didSetupConstraints { + self.setupConstraints() + self.didSetupConstraints = true + } + super.updateViewConstraints() + } + + func setupConstraints() { + // Override point + } +} diff --git a/waosSwift/modules/core/CoreFlow.swift b/waosSwift/modules/core/CoreFlow.swift index 7ca639e..54f7941 100644 --- a/waosSwift/modules/core/CoreFlow.swift +++ b/waosSwift/modules/core/CoreFlow.swift @@ -29,24 +29,24 @@ final class CoreFlow: Flow { } private func navigateToDashboard() -> FlowContributors { - let counterFlow = TasksViewFlow(withServices: self.services) - let githubSearchFlow = SecondViewFlow(withServices: self.services) + let tasksFlow = TasksFlow(withServices: self.services) + let secondFlow = SecondFlow(withServices: self.services) - Flows.whenReady(flow1: counterFlow, flow2: githubSearchFlow) { [unowned self] (root1: UINavigationController, root2: UINavigationController) in - let tabBarItem1 = UITabBarItem(title: L10n.firstTitle, image: nil, selectedImage: nil) + Flows.whenReady(flow1: tasksFlow, flow2: secondFlow) { [unowned self] (root1: UINavigationController, root2: UINavigationController) in + let tabBarItem1 = UITabBarItem(title: L10n.taskTitle, image: nil, selectedImage: nil) let tabBarItem2 = UITabBarItem(title: L10n.secondTitle, image: nil, selectedImage: nil) root1.tabBarItem = tabBarItem1 - root1.title = L10n.firstTitle + root1.title = L10n.taskTitle root2.tabBarItem = tabBarItem2 root2.title = L10n.secondTitle self.rootViewController.setViewControllers([root1, root2], animated: false) } - return .multiple(flowContributors: [.contribute(withNextPresentable: counterFlow, - withNextStepper: OneStepper(withSingleStep: SampleStep.tasksListIsRequired)), - .contribute(withNextPresentable: githubSearchFlow, - withNextStepper: OneStepper(withSingleStep: SampleStep.secondViewIsRequired))]) + return .multiple(flowContributors: [.contribute(withNextPresentable: tasksFlow, + withNextStepper: OneStepper(withSingleStep: SampleStep.tasksIsRequired)), + .contribute(withNextPresentable: secondFlow, + withNextStepper: OneStepper(withSingleStep: SampleStep.secondIsRequired))]) } } diff --git a/waosSwift/modules/core/CoreService.swift b/waosSwift/modules/core/CoreService.swift new file mode 100644 index 0000000..2775f5d --- /dev/null +++ b/waosSwift/modules/core/CoreService.swift @@ -0,0 +1,15 @@ +// +// Service.swift +// RxTodo +// +// Created by Suyeol Jeon on 12/01/2017. +// Copyright © 2017 Suyeol Jeon. All rights reserved. +// + +class CoreService { + unowned let provider: AppServicesProviderType + + init(provider: AppServicesProviderType) { + self.provider = provider + } +} diff --git a/waosSwift/modules/core/CoreSteps.swift b/waosSwift/modules/core/CoreSteps.swift index bcfd8c1..c8202f4 100644 --- a/waosSwift/modules/core/CoreSteps.swift +++ b/waosSwift/modules/core/CoreSteps.swift @@ -7,8 +7,8 @@ enum SampleStep: Step { case dashboardIsRequired - case tasksListIsRequired + case tasksIsRequired - case secondViewIsRequired + case secondIsRequired } diff --git a/waosSwift/modules/core/CoreViewController.swift b/waosSwift/modules/core/CoreViewController.swift deleted file mode 100644 index 1fa77fe..0000000 --- a/waosSwift/modules/core/CoreViewController.swift +++ /dev/null @@ -1,13 +0,0 @@ -import UIKit - -class CoreViewController: UIViewController { - lazy private(set) var className: String = { - return type(of: self).description().components(separatedBy: ".").last ?? "" - }() - - var disposeBag = DisposeBag() - - deinit { - log.info("DEINIT: \(self.className)") - } -} diff --git a/waosSwift/modules/onBoarding/Controllers/OnBoardingController.swift b/waosSwift/modules/onBoarding/Controllers/OnBoardingController.swift new file mode 100755 index 0000000..5ae5933 --- /dev/null +++ b/waosSwift/modules/onBoarding/Controllers/OnBoardingController.swift @@ -0,0 +1,181 @@ +/** + * Dependencies + */ + +import UIKit +import Reusable +import ReactorKit + +/** + * Crontroller + */ + +final class OnboardingController: CoreController, View, Stepper { + + // MARK: UI + + let introLabel = UILabel().then { + $0.numberOfLines = 4 + } + let completeButton = UIButton().then { + $0.setTitle(L10n.onBoardingValidation, for: .normal) + $0.layer.cornerRadius = 5 + $0.backgroundColor = UIColor.gray + } + let scrollView = UIScrollView().then { + $0.isPagingEnabled = true + $0.showsVerticalScrollIndicator = false + $0.showsHorizontalScrollIndicator = false + $0.backgroundColor = .clear + } + let pageControl = UIPageControl().then { + //$0.frame = CGRect(x: 100, y: 100, width: 200, height: 600) + $0.numberOfPages = 3 + $0.currentPage = 0 + $0.tintColor = .red + $0.pageIndicatorTintColor = UIColor.lightGray + $0.currentPageIndicatorTintColor = UIColor.gray + $0.backgroundColor = .clear + } + + // MARK: Properties + + let steps = PublishRelay() + + // MARK: Initializing + + init(reactor: OnboardingReactor) { + super.init() + self.reactor = reactor + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + + self.view.backgroundColor = .white + self.scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(3), height: view.frame.height) + + self.view.addSubview(self.scrollView) + self.view.addSubview(self.completeButton) + self.view.addSubview(self.pageControl) + self.view.addSubview(self.introLabel) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + override func setupConstraints() { + scrollView.snp.makeConstraints { (make) in + make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) + make.top.equalTo(self.view.safeAreaLayoutGuide.snp.top) + make.left.equalTo(self.view.safeAreaLayoutGuide.snp.left) + make.right.equalTo(self.view.safeAreaLayoutGuide.snp.right) + } + pageControl.snp.makeConstraints { (make) in + make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom).offset(-100) + make.height.equalTo(50) + make.left.equalTo(50) + make.right.equalTo(-50) + + } + introLabel.snp.makeConstraints { (make) -> Void in + make.width.height.equalTo(250) + make.center.equalTo(self.view) + } + completeButton.snp.makeConstraints { (make) in + make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom).offset(-25) + make.height.equalTo(50) + make.left.equalTo(50) + make.right.equalTo(-50) + } + } + + // MARK: Binding + + func bind(reactor: OnboardingReactor) { + bindView(reactor) + bindAction(reactor) + bindState(reactor) + } +} + +/** + * Extensions + */ + +private extension OnboardingController { + + // MARK: views (View -> View) + + func bindView(_ reactor: OnboardingReactor) { + pageControl.rx.controlEvent(.valueChanged) + .subscribe(onNext: { [weak self] in + guard let currentPage = self?.pageControl.currentPage else { + return + } + self?.scrollView.setCurrentPage(currentPage, animated: true) + }) + .disposed(by: self.disposeBag) + + scrollView.rx.currentPage + .subscribe(onNext: { [weak self] in + self?.pageControl.currentPage = $0 + }) + .disposed(by: self.disposeBag) + } + + // MARK: actions (View -> Reactor) + + func bindAction(_ reactor: OnboardingReactor) { + completeButton.rx.tap + .map { _ in Reactor.Action.complete } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + + scrollView.rx.didEndDecelerating + .map { _ in Reactor.Action.update(self.pageControl.currentPage) } + .bind(to: reactor.action) + .disposed(by: self.disposeBag) + + } + + // MARK: states (Reactor -> View) + + func bindState(_ reactor: OnboardingReactor) { + reactor.state + .map { $0.step } + .bind(to: self.steps) + .disposed(by: disposeBag) + + reactor.state.asObservable().map { $0.content } + .distinctUntilChanged() + .bind(to: self.introLabel.rx.text) + .disposed(by: self.disposeBag) + } +} + +extension Reactive where Base: UIScrollView { + var currentPage: Observable { + return didEndDecelerating.map({ + let pageWidth = self.base.frame.width + let page = floor((self.base.contentOffset.x - pageWidth / 2) / pageWidth) + 1 + return Int(page) + }) + } +} + +extension UIScrollView { + func setCurrentPage(_ page: Int, animated: Bool) { + var rect = bounds + rect.origin.x = rect.width * CGFloat(page) + rect.origin.y = 0 + scrollRectToVisible(rect, animated: animated) + } +} diff --git a/waosSwift/modules/onBoarding/Controllers/OnBoardingIntroViewController.swift b/waosSwift/modules/onBoarding/Controllers/OnBoardingIntroViewController.swift deleted file mode 100755 index 7c6a1dc..0000000 --- a/waosSwift/modules/onBoarding/Controllers/OnBoardingIntroViewController.swift +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Dependencies - */ - -import UIKit -import Reusable -import ReactorKit - -/** - * Crontroller - */ - -final class OnboardingIntroViewController: CoreViewController, StoryboardView, StoryboardBased, Stepper { - - // MARK: UI - - @IBOutlet weak var completeButton: UIButton! - - // MARK: Properties - - let steps = PublishRelay() - - // MARK: Initializing - - override func viewDidLoad() { - super.viewDidLoad() - - self.reactor = OnboardingIntroViewReactor() - } - - // MARK: Binding - - func bind(reactor: OnboardingIntroViewReactor) { - bindAction(reactor) - bindState(reactor) - } -} - -/** - * Extensions - */ - -private extension OnboardingIntroViewController { - - // MARK: actions (View -> Reactor) - - func bindAction(_ reactor: OnboardingIntroViewReactor) { - completeButton.rx.tap - .map { _ in Reactor.Action.introIsComplete } - .bind(to: reactor.action) - .disposed(by: self.disposeBag) - } - - // MARK: states (Reactor -> View) - - func bindState(_ reactor: OnboardingIntroViewReactor) { - reactor.state - .map { $0.step } - .bind(to: self.steps) - .disposed(by: disposeBag) - } -} diff --git a/waosSwift/modules/onBoarding/Flows/OnBoardingFlow.swift b/waosSwift/modules/onBoarding/Flows/OnBoardingFlow.swift index 90602ee..2bd387b 100644 --- a/waosSwift/modules/onBoarding/Flows/OnBoardingFlow.swift +++ b/waosSwift/modules/onBoarding/Flows/OnBoardingFlow.swift @@ -44,11 +44,10 @@ final class OnboardingFlow: Flow { } private func navigationToOnboardingIntroScreen() -> FlowContributors { - let onboardingIntroViewController = OnboardingIntroViewController.instantiate() - - onboardingIntroViewController.title = L10n.onBoardingTitle - self.rootViewController.pushViewController(onboardingIntroViewController, animated: false) - return .one(flowContributor: .contribute(withNextPresentable: onboardingIntroViewController, - withNextStepper: onboardingIntroViewController)) + let reactor = OnboardingReactor() + let viewController = OnboardingController(reactor: reactor) + viewController.title = L10n.onBoardingTitle + self.rootViewController.pushViewController(viewController, animated: false) + return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: viewController)) } } diff --git a/waosSwift/modules/onBoarding/Reactors/OnBoardingIntroViewReactor.swift b/waosSwift/modules/onBoarding/Reactors/OnBoardingReactor.swift similarity index 50% rename from waosSwift/modules/onBoarding/Reactors/OnBoardingIntroViewReactor.swift rename to waosSwift/modules/onBoarding/Reactors/OnBoardingReactor.swift index 0d91e95..1fc30f4 100755 --- a/waosSwift/modules/onBoarding/Reactors/OnBoardingIntroViewReactor.swift +++ b/waosSwift/modules/onBoarding/Reactors/OnBoardingReactor.swift @@ -5,27 +5,32 @@ import Foundation import ReactorKit +let contents: [String] = [L10n.onBoardingIntroduction, "toto", "titi"] + /** * Reactor */ -final class OnboardingIntroViewReactor: Reactor, ServicesProviderType { +final class OnboardingReactor: Reactor, ServicesProviderType { // MARK: Constants // user actions enum Action { - case introIsComplete + case complete + case update(Int) } // state changes enum Mutation { - case moveDashboard + case goToDashboard + case setContent(Int) } // the current view state struct State { var step: Step = SampleStep.introIsRequired + var content: String = contents[0] } // MARK: Properties @@ -35,22 +40,27 @@ final class OnboardingIntroViewReactor: Reactor, ServicesProviderType { // MARK: Action -> Mutation (mutate() receives an Action and generates an Observable) - func mutate(action: OnboardingIntroViewReactor.Action) -> Observable { + func mutate(action: OnboardingReactor.Action) -> Observable { switch action { - case .introIsComplete: + case .complete: preferencesService.onBoarded = true - return Observable.just(Mutation.moveDashboard) + return Observable.just(Mutation.goToDashboard) + case let .update(page): + return Observable.just(Mutation.setContent(page)).delay(0.1, scheduler: MainScheduler.instance) } } // MARK: Mutation -> State (reduce() generates a new State from a previous State and a Mutation) - func reduce(state: OnboardingIntroViewReactor.State, mutation: OnboardingIntroViewReactor.Mutation) -> OnboardingIntroViewReactor.State { + func reduce(state: OnboardingReactor.State, mutation: OnboardingReactor.Mutation) -> OnboardingReactor.State { var state = state switch mutation { - case .moveDashboard: + case .goToDashboard: state.step = SampleStep.introIsComplete return state + case let .setContent(page): + state.content = contents[page] + return state } } } diff --git a/waosSwift/modules/onBoarding/Storyboards/OnboardingIntroViewController.storyboard b/waosSwift/modules/onBoarding/Storyboards/OnboardingIntroViewController.storyboard deleted file mode 100755 index e0f0c68..0000000 --- a/waosSwift/modules/onBoarding/Storyboards/OnboardingIntroViewController.storyboard +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/waosSwift/modules/secondController/Controllers/SecondController.swift b/waosSwift/modules/secondController/Controllers/SecondController.swift new file mode 100644 index 0000000..f12ca0f --- /dev/null +++ b/waosSwift/modules/secondController/Controllers/SecondController.swift @@ -0,0 +1,73 @@ +/** + * Dependencies + */ + +import UIKit +import ReactorKit + +/** + * Controller + */ + +class SecondController: CoreController, View { + + // MARK: UI + + let label = UILabel().then { + $0.text = L10n.secondTitle + $0.textAlignment = .center + } + + // MARK: Initializing + + init(reactor: SecondReactor) { + super.init() + self.reactor = reactor + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: View Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .white + self.view.addSubview(self.label) + } + + override func setupConstraints() { + label.snp.makeConstraints { (make) -> Void in + make.width.height.equalTo(250) + make.center.equalTo(self.view) + } + } + + // MARK: Binding + + func bind(reactor: SecondReactor) { + bindView(reactor) + bindAction(reactor) + bindState(reactor) + } +} + +/** + * Extensions + */ + +private extension SecondController { + + // MARK: views (View -> View) + + func bindView(_ reactor: SecondReactor) {} + + // MARK: actions (View -> Reactor) + + func bindAction(_ reactor: SecondReactor) {} + + // MARK: states (Reactor -> View) + + func bindState(_ reactor: SecondReactor) {} +} diff --git a/waosSwift/modules/secondController/Controllers/SecondViewController.swift b/waosSwift/modules/secondController/Controllers/SecondViewController.swift deleted file mode 100644 index 97bfa7f..0000000 --- a/waosSwift/modules/secondController/Controllers/SecondViewController.swift +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Dependencies - */ - -import UIKit -import Reusable -import ReactorKit - -/** - * Controller - */ - -class SecondViewController: CoreViewController, StoryboardView, StoryboardBased { - - override func viewDidLoad() { - super.viewDidLoad() - } - - func bind(reactor: SecondViewReactor) { - bindAction(reactor) - bindState(reactor) - bindView(reactor) - } -} - -/** - * Extensions - */ - -private extension SecondViewController { - - // MARK: views (View -> View) - - func bindView(_ reactor: SecondViewReactor) {} - - // MARK: actions (View -> Reactor) - - func bindAction(_ reactor: SecondViewReactor) {} - - // MARK: states (Reactor -> View) - - func bindState(_ reactor: SecondViewReactor) {} -} diff --git a/waosSwift/modules/secondController/Flows/SecondViewFlow.swift b/waosSwift/modules/secondController/Flows/SecondFlow.swift similarity index 75% rename from waosSwift/modules/secondController/Flows/SecondViewFlow.swift rename to waosSwift/modules/secondController/Flows/SecondFlow.swift index 306ecd0..f5bff3e 100644 --- a/waosSwift/modules/secondController/Flows/SecondViewFlow.swift +++ b/waosSwift/modules/secondController/Flows/SecondFlow.swift @@ -9,7 +9,7 @@ import UIKit * Flow */ -final class SecondViewFlow: Flow { +final class SecondFlow: Flow { var root: Presentable { return self.rootViewController } @@ -31,18 +31,18 @@ final class SecondViewFlow: Flow { switch step { - case .secondViewIsRequired: - return navigateToSecondViewScreen() + case .secondIsRequired: + return navigateToSecondScreen() default: return FlowContributors.none } } - private func navigateToSecondViewScreen() -> FlowContributors { - let viewController = SecondViewController.instantiate() + private func navigateToSecondScreen() -> FlowContributors { + let reactor = SecondReactor() + let viewController = SecondController(reactor: reactor) viewController.title = L10n.secondTitle - self.rootViewController.pushViewController(viewController, animated: true) - return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: OneStepper(withSingleStep: SampleStep.secondViewIsRequired))) + return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: OneStepper(withSingleStep: SampleStep.secondIsRequired))) } } diff --git a/waosSwift/modules/secondController/Reactors/SecondViewReactor.swift b/waosSwift/modules/secondController/Reactors/SecondReactor.swift similarity index 87% rename from waosSwift/modules/secondController/Reactors/SecondViewReactor.swift rename to waosSwift/modules/secondController/Reactors/SecondReactor.swift index 4f13694..0c93ef6 100644 --- a/waosSwift/modules/secondController/Reactors/SecondViewReactor.swift +++ b/waosSwift/modules/secondController/Reactors/SecondReactor.swift @@ -8,7 +8,7 @@ import ReactorKit * Reactor */ -final class SecondViewReactor: Reactor { +final class SecondReactor: Reactor { // MARK: Constants @@ -22,12 +22,20 @@ final class SecondViewReactor: Reactor { // the current view state struct State { + + init() { + } } // MARK: Properties let initialState = State() + // MARK: Initialization + + init() { + } + // MARK: Action -> Mutation (mutate() receives an Action and generates an Observable) // func mutate(action: Action) -> Observable { diff --git a/waosSwift/modules/secondController/Storyboards/SecondViewController.storyboard b/waosSwift/modules/secondController/Storyboards/SecondViewController.storyboard deleted file mode 100644 index 56746ac..0000000 --- a/waosSwift/modules/secondController/Storyboards/SecondViewController.storyboard +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -